First off: if you are using the XenApp 6 SDK exclusively for scripting your farm actions and configuration, then this blog entry will not apply to you. If, however, you are a programmer and you wish to use the XenApp 6 SDK to build an application that interacts with XenApp, then you, my friend, are the target audience!
Note: Be sure you are using XenApp 6 SDK Version 6.1.2 when following along with this blog. The previous versions have bugs that prevent some of the programming features shown from working.
For those who have programmed against MFCOM in the past, programming against the PowerShell API is quite different. MFCOM exposes a COM API with an object model that can be traversed, from the IMetaFrameFarm interface, through an object hierarchy, eventually reaching the “leaf nodes” such as applications, servers, and sessions. Once you reach an object of interest, you call a method exposed by that object to perform an action. In PowerShell the approach is much more task-based. You start by choosing a task to perform, and then you either provide a wildcard to select objects that the task should operate on, or alternatively, pass objects into the task.
Another big change is that MFCOM calls are simply APIs that the programmer calls directly. In PowerShell, the approach is a bit more complicated. The programmer must:
- Create a Runspace, with the relevant snap-ins loaded. This is relatively expensive; but assuming the Runspace executes on the local machine this can live for the lifetime of your program, and can be reused any time your program needs to execute PowerShell commands. (I’ll get into remote Runspaces in a future blog about PowerShell Remoting.)
- Create a Pipeline within the Runspace. Each time that you want to perform an atomic operation, you create a pipeline. A pipeline is equivalent to one command that you would type in the PowerShell command prompt.
- Add either individual commands or a script to the Pipeline.
- Invoke the Pipeline. When you call this method, you can optionally pass objects in, and it returns the objects that are output from the commands or script in the pipeline.
- Convert the objects that are output from the pipeline (which are all of type System.Management.Automation.PSObject) back into concrete .NET types.
When developing the XenApp 6 SDK, we recognized that this is quite a lot of effort, especially when you just want to do something simple. We also recognized that there is potential for errors because the command names, parameter names, and object property names are all just strings. It is easy to make a mistake and enter “Enalbed” instead of “Enabled”, and you won’t find that error until you actually run the program. Therefore, to make development easier and more robust, we have implemented a set of SDK “wrappers” that simplify programming against the XenApp SDK.
The first thing we provide is a simple, predefined “default” runspace for executing Citrix commands. This runspace will have the Citrix.XenApp.Commands and Citrix.Common.Commands snap-ins already loaded, and it is created on demand and lives for the lifetime of your application. You can reuse it as often as you need; the only limitation is that you should ensure that multi-threaded applications do not try to execute multiple commands within the runspace at the same time.
To use the default runspace, reference the System.Management.Automation.dll assembly from the Microsoft SDK and the Citrix.Management.Automation.dll assembly, which can typically be found at "C:\Program Files\Citrix\XenApp Server SDK\bin". Add “using” references to the following namespaces:
Then, use the static property CitrixRunspaceFactory.DefaultRunspace:
Note that there is also a method called CitrixRunspaceFactory.CreateLocalRunspace() that creates another runspace with the Citrix snap-ins loaded. You can use this in the rare case that you need multiple local runspaces, for instance to handle multiple threads.
Now that you have a Runspace, you need a Pipeline. PowerShell makes this trivial, so we do not provide a wrapper for this. Simply call the method CreatePipeline() on the runspace instance. The return value is a Pipeline object:
Into the pipeline you need to add one or more instances of System.Management.Automation.Runspaces.Command. To simplify the creation of Command objects, we provide an assembly that has classes capable of automatically generating them, pre-populated with XenApp 6 SDK PowerShell commands. This assembly is Citrix.XenApp.Sdk.dll and can be found in the same directory where Citrix.Management.Automation.dll is located. You will also find Citrix.Common.Sdk.dll which is the analogous assembly for the cmdlets in the Citrix.Common.Commands snap-in.
To use the Citrix.XenApp.Sdk.dll assembly, add a reference to it and a “using” statement for it:
If you use the Visual Studio object browser to inspect the contents of Citrix.XenApp.Sdk.dll, you will find that it contains classes matching every XenApp 6 PowerShell SDK cmdlet, and every parameter set of those cmdlets. For example, there is a class matching the cmdlet Get-XAApplication with the parameter set “ByName”; this class is named GetXAApplicationByName. It has properties named BrowserName and Command (among others). You use this class by creating an instance of it, setting the properties as you would set the parameter values on the PowerShell command line, then getting the value of the Command property. This returns an instance of the PowerShell Command object, already set up to execute the cmdlet represented by the class’ state. You can add one or more of those Command objects to the pipeline you created.
For example, to create a Command that gets all applications whose names match the pattern “A*”, you can do:
The reason that the BrowserName property is an array is that the PowerShell command can accept multiple inputs, even on the command line.
Add the commands to the pipeline using the pipeline’s Commands.Add method:
Once the pipeline contains the command(s) that you want to execute, you invoke it. The pipeline has an Invoke method, which you can optionally pass objects into. This works as you would expect it to, if you are familiar with piping objects into commands via the PowerShell command-line. The Invoke method also returns a list of objects, which are the output of the commands in the pipeline:
The next step is to take those objects from the output and convert them back into concrete .NET types. The objects as returned are all of type System.Management.Automation.PSObject which can be used as a type of generic property bag; that is, you can get and set properties within it, referencing those properties by name. For example, you can get the value of an application’s DisplayName property using (keeping in mind the output of pipeline.Invoke is a collection):
However, we do not wish to reference properties by name. It is too easy to make a mistake and introduce a bug that is not found until you run the application. You really want to use a concrete, typesafe .NET type to store the output objects.
You will need a definition of the types that are output from the cmdlets. You can find these types defined in an assembly named Citrix.XenApp.Commands.Data.dll, and in the equivalent Citrix.Common.Commands.Data.dll for the Citrix.Common.Commands snap-in. Add a reference to that assembly, and a “using” statement for it:
There are a couple of options as to how to convert the PSObject values into concrete types. One option is to use the PSObject.BaseObject property, and cast it to an SDK type.
This approach works, but only when the object is actually of the type that you are requesting. Rarely, what you will get from the pipeline is really a pseudo-type; one that has all of the same properties as the type you expect, but which is not actually an instance of the type you expect. This happens when some cmdlet in the pipeline must serialize the type. Typically this happens when using PowerShell remoting, or when using the Export-CliXml and Import-CliXml cmdlets.
To help you handle these pseudo-types, we have created a method that can convert them back into a XenApp SDK type. This method is implemented as an extension method on the PSObject type, named ConvertTo<T>():
This method is smart enough to do a direct cast if possible, otherwise, it will try very hard to convert the properties contained in the PSObject into the properties of an instance of the type you specify. I therefore recommend always using this method rather than just casting the BaseObject property.
You may wonder how you know what type the objects are that a cmdlet will output. This information is available in the cmdlet help; however, we have also built in some assistance here. If you used one of the “Command generator” classes in the Citrix.XenApp.Sdk.dll assembly, for instance the GetXAApplicationByName class, you will find it contains a method named ParseResult. This method takes in a list of PSObjects, and returns a list of the objects of the proper type which is expected to be output from that cmdlet. So you can rewrite the loop that handles the output as:
At this point, everything is typesafe, and the invocation steps are quite straightforward. The entire sample looks like this:
Hopefully you will agree, this is getting pretty simple and straightforward. But we still didn’t like that it takes several lines of code to execute a simple command. So, we added extension methods to the Runspace type which make this even easier. The methods are called ExecuteCommand and ExecutePipeline. These methods are not quite as powerful as doing all of the steps yourself; for instance, you cannot add arbitrary PowerShell commands, only those that are part of the XenApp 6 SDK. But they are extremely simple.
That’s all there is to it! We have tried to make programming against the XenApp 6 SDK as easy as scripting against it, and I hope that it is straightforward enough to enable 3rd party developers to leverage our SDK to create some really amazing applications as they have done in the past with MFCOM!