Use Infinity Web Services from PowerShell

Windows PowerShell has built in great support to invoke web services. Infinity applications such as Blackbaud CRM expose a rich suite of web services that you can easily invoke from PowerShell without the need for any dependencies on the machine where the PowerShell session runs.

This article shows how to use the PowerShell New-WebServiceProxy cmdlet with the Infinity AppFxWebService.asmx endpoint. (See Blackbaud AppFx Web Service (AppFxWebService.asmx).)

For more background information about Infinity Web APIs, see:

Introduction to the Infinity Web Service APIs

Or a for a more in depth discussion, see:

API Overview

Why Would You Want to Do This?

PowerShell is Microsoft's strategic technology for automation, scripting, and management on Windows. PowerShell is quickly becoming the tool of choice for IT professionals who configure, administer, and manage Windows environments and who want to maximize the level of automation in their operations. PowerShell has elegant support for combining .NET, classic COM, WMI, and existing command line utilities in a single unified scripting framework that scales from interactive command line use up to complex modularized scripting libraries.

Infinity web services can be invoked from any client programming technology that can utilize WSDL defined SOAP endpoints, including of course the Visual Studio languages of VB.Net and C#. But it might make sense for you to implement a solution using PowerShell instead of a Visual Studio language if you are most comfortable with the PowerShell environment or if the task at hand is in the area that is in PowerShell's power swing already, perhaps intersecting with other IT operational management tasks that will involve WMI, COM, or other PowerShell cmdlets. For example, if you author some kind of operational deployment script that configures an Infinity web server with the PowerShell WebAdministration module and as part of that script you need to invoke some of the Infinity application functionality such as adding an application user to an application System Role.

Prerequisites

This article focuses on the specific mechanics of combining PowerShell with the Infinity web service API, so it is best if you already have a basic familiarity with each of those independent technologies. 

To learn more about the Infinity web services API, see Introduction to the Infinity Web Service APIs.

To learn more about PowerShell's support for web services see these articles:

To follow along in a live Infinity system, you also need the following:

  • The full URL to the appfxwebservice.asmx endpoint for your system. For example, http://localhost/bbappfx/appfxwebservice.asmx.

  • A user account with permissions in the Infinity application. Security is enforced at the web services layer just as it is through the interactive GUI, so you need to be granted permission to any of the features that you will invoke or you will get a permission denied error.

Start with a (Simple) Ping

The AppFxWebService endpoint exposes a very simple method named "PingSimple" that takes as input a string and returns that same string with some time information from the server appended. This method is often used to verify that the server is up and running or to do a best-case round trip timing when troubleshooting performance issues. The PingSimple web service is a nice introductory target because it is so simple.

To invoke this method from PowerShell, the first thing we do is create the web service proxy object to use to invoke methods against, and for that we use the New-WebServiceProxy cmdlet. (I am going to use a variable named $url to store my URL so that if you copy and paste from these examples, it will be easy to port to your own live system.)

Step 1 -  Create the web service proxy.

#change this to the url for your system
$url="http://paulg2008r2/bbappfx_firebird/appfxwebservice.asmx"

$proxy=New-WebServiceProxy -Uri $url -Class "proxy" -Namespace "bbapi" `
-UseDefaultCredential

Here I use the –UseDefaultCredential option so that my integrated Windows Authentication login is used. You could optionally use the Get-Credential cmdlet along with the –Credential option to supply alternate credentials or to hit a system that only supports basic authentication.

If you use the command line shell, then after you create the proxy, you get Intellisense on the $proxy variable with all of the methods. So for example, if you type "$proxy.PingS" and press TAB, the shell auto-completes to "$proxy.PingSimple(" with an open paren ready to accept the string parameter to pass to the web method:

Auto-complete of the web service method name

Step 2 -  Invoke the method.

To invoke the method, just enter a string after the open paren, close the paren, and press Enter.

Response from server after calling the PingSimple method

Note that the first time you invoke one of the methods from the proxy, a slight delay occurs while the PowerShell run time does the necessary metadata interpretation and code generation magic that enables this function-like syntax over the underlying SOAP protocol. After the first call, all of that metadata is cached and subsequent calls will be bound only by the server response time.

Use the *request / * Reply Style Web Service Methods

Unlike the PingSimple method, most of the Infinity web service methods have a signature that looks something like this:

VB

Function WebMethodX(request As WebMethodXRequest) As WebMethodXReply

C#

WebMethodXReply WebMethodX(WebMethodXRequest request )

where "WebMethodXRequest" is a rich class with properties that define all of the parameters to the method. 

For example, the method signature for the CodeTableEntryGetId web service that translates a code table entry such as the Constituent Biographical Title Code "Mr." into the GUID ID value for that entry looks like this:

Function CodeTableEntryGetID(CodeTableEntryGetIDRequest As CodeTableEntryGetIDRequest) 
As CodeTableEntryGetIDReply

Where the request and reply objects are defined as follows (if using VB.Net syntax):

Class CodeTableEntryGetIDRequest
    Inherits ServiceRequest

    Public CodeTableName As String
    Public Description As String
End Class

Class CodeTableEntryGetIDReply

    Public ID As Guid
End Class

Or the equivalent C# syntax:

class CodeTableEntryGetIDRequest : ServiceRequest
{
    public string CodeTableName;
    public string Description;

}

class CodeTableEntryGetIDReply
{
    public Guid ID;

}

The CodeTableEntryGetIdRequest class has two fields that represent the parameters into the web service, and the web service method itself only takes a single parameter, which is an instance of the CodeTableEntryGetIdRequest class.

This is sometimes called the "document style" or "message style" or "explicit style" pattern because it is explicit that this method accepts a payload as input and returns a payload as output, as opposed to exposing an RPC style syntax where traditional arguments are implicitly mapped to the input payload by the run time.

This means that the typical call sequence to an Infinity web service would look something like this pseudo-code:

  1. Create instance of request object

  2. Set properties on request

  3. Call method and pass in request

  4. Examine properties of returned reply object

The next web service method we will call from PowerShell will use this pattern.

Ping Using the Request/Reply Pattern Style Method

The AppFxWebService exposes a method named "Ping" that, unlike the PingSimple method, follows this request/reply pattern. To invoke it, we will need to do the following:

  1. Create instance of the PingRequest object

  2. Set properties on the PingRequest object

  3. Call the Ping method and pass in the PingRequest object

  4. Examine properties of returned reply object

You use the New-Object cmdlet to create an instance of the PingRequest class. When we invoked the New-WebServiceProxy cmdlet we specified "bbapi" as the –NameSpace parameter, so the syntax for creating the PingRequest is as follows:

$pingRequest= New-Object bbapi.PingRequest

If you need to know the properties that are available on the request object, you can always pipe the object to the Get-Member cmdlet to take a look.

Examine the request properties with Get-Member

You can see that the PingRequest has three properties:

  1. ClientAppInfo

  2. ConnectToDatabase

  3. MessageToEcho

The ClientAppInfo property is of type bbapi.ClientAppInfoHeader. This is another rich class that has its own properties that must be configured, so we need to create an instance of this class using New-Object, and we can examine the properties with Get-Member:

The most important properties are the ClientAppName and the REDatabaseToUse. For our first Ping invocation, we will ignore the REDatabaseToUse and just specify the ClientAppName. Then we will assign the header to the ClientAppInfo property of the PingRequest and set the other properties. The full script looks like this:

PowerShell script to invoke the Ping web service

We can see that the reply object has a property named EchoedMessage. If we examine that property, we see that the method succeeded and the server replied.

Get the EchoedMessage value from the PingReply

Get Available Databases

It is rare but possible that an Infinity endpoint could support multiple different databases from the same URL. This possibility is reflected in the API in the fact that almost every web service method requires you to pass in a unique key that identifies the intended target database. Note that the value of this key is not the physical SQL Server database name but rather a key/alias that looks up the actual SQL Server database name. On my system, I have three databases configured and thus I have three possible keys. I need to supply one to every web service API call I make if that method will touch a database. In the Web Shell UI on my system, I get prompted at login with a menu that presents all three keys:

Web Shell prompt when the URL is configured for multiple databases

The classic ClickOnce shell presents this list as a drop-down list.

The value of the key I need for the API will be one of the values listed in the drop-down or on the Web Shell prompt page. I can use the GetAvailableREDatabases web service method to get this list.

Use GetAvailableREDatabases to get a list of databases serviced by the web service endpoint

Ping and Connect to the Database

Now that I know a few specific database keys, I can use the Ping method and set the ConnectToDatabase property to $true on the request so that the Ping not only connects to the web server but also verifies connectivity from the web server to the database.

Use ConnectToDatabase=$true with Ping

Get Data from the DataListLoad Method

Now that we understand the basic pattern of how to call the Infinity web services, we can do something a little more useful than Ping. Suppose we want to find out if our database includes any users who are not system administrators but are also not in any system roles. Those users would not be able to do anything, so some cleanup is probably necessary on those records. We should either remove the users or place them into one or more system roles. In the Web Shell GUI, a page in the Administration\Security functional area lists all users.

Application users data list

The metadata page for this list shows the ID and name of the list. It is a data list named "Application Users List."

Metadata for the Application Users List

The following script gets the data from this data list, and it will use the Where-Object cmdlet to filter down to those users that are not sysadmins but who are also not in any system roles:

Script to query the Application Users data list

Conclusion

Windows PowerShell is a great tool to call web services. Infinity applications expose a lot of functionality through web services. It's not hard to combine the two together in scenarios where it makes sense.