Windows PowerShell has built into it great support for invoking web services. Infinity applications such as Blackbaud Enterprise CRM expose a rich suite of web services that can easily be invoked from PowerShell without the need for any dependencies on the machine where the PowerShell session is running. This article will show how to use the PowerShell New-WebServiceProxy cmdlet with the Infinity AppFxWebService.asmx endpoint.
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 are responsible for configuring, administering, and managing Windows environments and who are looking to maximize the level of automation of 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 are authoring some kind of operational deployment script that configures an Infinity web server using 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.
This article will focus 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 the documentation on the BBDN web site:
To learn more about PowerShell’s support for web services see these articles:
To follow along against a live Infinity system you will 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 will need to be granted permission to any of the features that you will be invoking 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.
In order to invoke this method from PowerShell the first thing we need to do is create the web service proxy object that will be used 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 am using the –UseDefaultCredential option so that my integrated Windows Authentication login will be used. You could optionally use the Get-Credential cmdlet along with the –Credential option if you need to supply alternate credentials or if you are hitting a system that only supports basic authentication.
If you are using the command line shell then once you have created the proxy you will get intellisense on the $proxy variable with all of the methods, so for example if you type “$proxy.PingS” and hit TAB the shell will auto-complete to “$proxy.PingSimple(“ with an open paren ready to accept the string parameter to pass to the web method:
Figure 1: 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 hit enter:
Figure 2: Response from server after calling the PingSimple method
Note that the first time you invoke one of the methods from the proxy there will be a slight delay while the PowerShell runtime 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.
Using 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:
Function WebMethodX(request As WebMethodXRequest) As WebMethodXReply
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 which 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
The CodeTableEntryGetIdRequest class has two fields which 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 runtime.
This means that the typical call sequence to an Infinity web service would look something like this pseudocode:
- Create instance of request object
- Set properties on request
- Call method and pass in request
- 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. In order to invoke it we will need to do the following:
- Create instance of the PingRequest object
- Set properties on the PingRequest object
- Call the Ping method and pass in the PingRequest object
- 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:
Figure 3: Examining the request properties with Get-Member
You can see that the PingRequest has three properties:
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:
Figure 4: 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:
Figure 5: Getting 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 will be used to lookup the actual SQL Server database name. On my system I have 3 databases configured and thus I have 3 possible keys and I will need to supply one to every web service API call I make if that method will end up touching the database. In the webshell UI on my system I get prompted at login with a menu that presents all 3 keys:
Figure 6: WebShell prompt when the url is configured for multiple databases
The classic ClickOnce shell presents this list as a dropdown:
The value of the key I need for the API will be one of the values listed in the dropdown or on the webshell prompt page. I can use the GetAvailableREDatabases web service method to get this list.
Figure 7: Using 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 will not only connect to the web server but also verify connectivity from the web server to the database.
Figure 8: Using 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 there are any users in our database that are not System Administrators but are also not in any system roles. Those users would not be able to do anything, so there probably needs to be some cleanup done on those records. Either they should be removed as users or placed into one or more system roles. In the WebShell GUI there is a page located in the Administration\Security functional area that shows a list of all users:
Figure 9: Application Users datalist
The metadata page for this list shows the ID and name of the list, it is a datalist named “Application Users List”:
Figure 10: Metadata for the Application Users List
The following script will get the data from this datalist 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:
Figure 11: Script to query the Application Users Datalist
Windows PowerShell is a great tool for calling web services. Infinity applications expose a lot of functionality via web services. It’s not hard to combine the two together in scenarios where it makes sense.