This blog is intended for programmers who wish to use the XenApp 6 SDK within their programs to configure a farm remotely.
In my last blog, I went over the set-up and usage of the XenApp 6 SDK for scripters who wish to use the commands remotely. Today I’ll cover the same scenario, but used within a .NET program that invokes the SDK.
Let’s review the steps for programmatically invoking a cmdlet locally:
- Create a Runspace with the relevant snap-ins loaded.
- Create a Pipeline within the Runspace.
- Add individual commands or a script to the Pipeline.
- Invoke the Pipeline.
- Convert the output objects back into concrete .NET types.
The sequence is almost identical for invoking a cmdlet remotely, but there are a few minor differences.
Create the Runspace(Pool) with the snap-ins loaded
Runspaces are somewhat expensive to create, but when you are invoking commands locally, you can simply reuse the same Runspace for the entire lifetime of your program. However when your Runspace is remote, you can run into problems. You may experience a network split or other interruption in communication between the client and the server. When this happens, the Runspace ceases to function. The Runspace class in PowerShell is not capable of automatically correcting itself when a fault occurs, so a program using a remote Runspace needs to be prepared to detect and correct faults itself, by recreating and re-initializing the Runspace. Additionally, if there are many clients connecting to a single server, you can end up with a large number of Runspaces existing on the server, but very infrequently used. This is a waste of resources.
To make this simpler, a program can use a RunspacePool. This is a new PowerShell 2.0 concept that both provides automatic correction of faults (by recreating Runspaces), and also provides Runspace pooling so that when Runspaces are not in use they can be destroyed and recreated as necessary. It is recommended that programs using PowerShell remoting should utilize RunspacePools instead of Runspaces for these reasons.
Within the Citrix.Management.Automation.dll assembly introduced in the previous XenApp 6 SDK – Programming with .NET blog, you can find a method called CitrixRunspaceFactory.CreateRunspacePool(string serverName). The serverName parameter is the name of the server on which to create the runspace.
Important Note: this method is currently hardcoded to not validate the server’s SSL certificate. This is unfortunate; in a future revision, these methods will have an option to fully validate the SSL certificate. However, for now, if you are not using a self-signed certificate and want to create a secure remote RunspacePool, add this method to your program and use it rather than CitrixRunspaceFactory.CreateRunspacePool():
Create a pipeline (PowerShell class) that uses your RunspacePool
Once you have a RunspacePool, you can attach that to another new PowerShell 2.0 class named PowerShell. This class takes the place of the Pipeline class that is used with a Runspace. The PowerShell class has a property named RunspacePool which can be set to the RunspacePool that you created.
Add individual commands (or a script) to the pipeline
This is very similar to using the Pipeline class with a Runspace:
The “command” object can be created using the classes in the Citrix.XenApp.Sdk.dll assembly as explained in the previous blog. For example:
A limitation to be aware of here is that, due to the way that PowerShell serializes types, the InputObject parameter on most XenApp SDK commands will not function properly when remoting the command.
Invoke the pipeline
Again, very similar to the Pipeline class:
Objects can be passed into the Invoke() method; these objects will be serialized and sent into the Pipeline in the server’s Runspace. XenApp SDK commands are capable of using these serialized objects the same as if they were the “real” object instances.
Convert the output objects back into concrete .NET types
This is one of the most difficult aspects of using PowerShell remoting within a program. The problem is that PowerShell does not actually create the objects on the client using the original type. Instead, it fabricates new dynamic types that have all of the same properties and methods, with the former accessed from the PSObject “property bag” and the latter implemented as proxies to the actual object instance on the server side. This means that a simple cast of the PSObject.BaseObject property will not succeed.
To make matters more difficult, only simple types are remoted with the actual type intact. Enums and complex types are converted to strings using the value’s ToString() method; arrays are converted to arrays of strings using each array element’s ToString() method. Doing the necessary conversion back to concrete types is quite complex.
We have done what we could to make this process easier. First of all, we have eliminated the use of complex types as property values in all of the XenApp SDK object types, whenever possible. Secondly, we have provided comprehensive “type-rebuilder” logic within Citrix.Management.Automation.dll. The same extension method we mentioned before on PSObject – ConvertTo<T>() – is capable of transforming the dynamic types that come out of PowerShell remoting back into the concrete XenApp types.
The bottom line is that, for most purposes, the best way to handle the output objects when using PowerShell remoting is to use the ConvertTo<T>() method.
As with Runspaces, we have gone one step further to make RunspacePools work with minimal extra code overhead, by adding ExecuteCommand() and ExecutePipeline() methods as extension methods on the RunspacePool type. So you can convert the example from the previous blog into one that works remotely by making only a few minor tweaks:
Local RunspacePools and remote Runspaces
To avoid having to have different code paths for local vs remote SDK usage, Microsoft has added the ability to have local RunspacePools and remote Runspaces.
Local RunspacePools have no real runtime benefit over local Runspaces when using the XenApp SDK, but to make code more consistent you can use the CitrixRunspaceFactory.DefaultRunspacePool property to obtain a local RunspacePool with the Citrix snap-ins loaded. You can then use the RunspacePool the same as you would with a remote RunspacePool.
Although remote Runspaces present a few difficulties in their use (fault tolerance, etc), you can create one. There is a method called CitrixRunspaceFactory.CreateRemoteRunspace(string serverName) available; however, it suffers from the same limitation as the CitrixRunspaceFactory.CreateRemoteRunspacePool(string serverName) method: it does not validate the server’s SSL certificate and is therefore insecure. To create a secure remote Runspace, use the following code instead:
If your program is using a mixture of commands from the Citrix.XenApp.Commands snap-in and the Citrix.Common.Commands snap-in, you will need to execute the latter within a local runspace. Just as for scripters, the Citrix.Common.Commands cmdlets are not available remotely.
Remote execution of cmdlets is slower than local execution, so if you are converting a program from operating locally to operating remotely, you should expect some reduction in performance.
Remember that programs with restricted execution rights (such as IIS-hosted web services) cannot use PowerShell remoting. We are currently working on a comprehensive fix to this problem.
Just as for scripters, we have put a lot of effort into getting the developer experience for remote PowerShell execution to be as painless as possible. I hope that you, the community of 3rd party developers, can leverage this work to simplify your use of the SDK and successfully leverage it for your programs!