Post-XenApp5 release, I’ve had the opportunity to do some research into virtualizing and automating components of the Citrix datacenter, with a specific focus on three Citrix products:  XenServer5.0, XenApp5.0, and Provisioning Server.  The idea, first passed to me by my colleague JoelK, is to virtualize my XenApp5.0 farm within a XenServer pool, while using Provisioning Server to provision and de-provision farm members.  Through the use of automated scripts along with Citrix Provisioning Server, I can automate the bootup and shutdown of the farm members, with the end result being that we can finally define a dynamic datacenter:  a datacenter that automatically expands and contracts according to the level of user demand for resources.
I expect this to be the first of a few posts on some things I’ve picked up along the way to the ultimate goal of automating the Citrix Datacenter.  As such, this is a very beginner look at how to setup a client for the Citrix Provisioning Server using the client APIs that are available for download from your Citrix Provisioning Server.

The client API provides a way to remotely script the Provisioning Server action commands that are available as if you were using the Provisioning Server Console, or the MCLI.exe utility installed with Provisioning Server.  More information about what MCLI.exe can do is available in the Citrix Provisioning Server Admin Guide (or via commandline: MCLI.exe /help from the Provisioning Server install directory).

The two operations that I chose to script remotely are the following:
Add a device to a device collection: 
MCLI.exe Add Device /r DeviceName=”APITestDevice” SiteName=”Sys2LabSite” CollectionName=”2k3 Base” Description=”This device was created to test the Provisioning API.” DeviceMac=”DE-AD-BE-EF-CA-FE”

Add that device to Active Directory Users and Computers: 
MCLI.exe Run AddDeviceToDomain /p DeviceName=APITestDevice


PRELIMINARY STEPS
Before we start the C# project, we have to get the environment settled…

Obtain the ServiceModel Metadata utility (svcutil.exe) from the Windows SDK
First, you have to download the ServiceModel Metadata utility (svcutil.exe) in order to grab the client code from the Provisioning Server.  The ServiceModel Metadata utility can be found at the Windows SDK install location (default: C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin.  You can download the Windows SDK from here: http://www.microsoft.com/downloads/details.aspx?FamilyId=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en

Run svcutil.exe against the Provisioning Server
From the command line on the Provisioning Server, run the following command: svcutil.exe http://localhost:8000/pvs/mapi/commandset?wsdl
This action downloads the necessary client APIs for setting up your own Citrix Provisioning Server client (ServiceMain.cs, output.config)

Build your Citrix Provisioning client

Now that you have the necessary client APIs, you can start writing some code that will use it.  Included below is a simple example for scripting the commands mentioned above for creating a device and adding the computer account to Active Directory.

Make sure to have your downloaded files in the same namespace, and reference them correctly in your project.

Setup downloaded files:
Within the .config file, you will need to change the server url to your specific Provisioning Server URL.  Add a reference to the .config file within the C# project.
Also, add a reference to the ServiceMain.cs file within the project.


START CODING!!!
Throughout the remaining portion of this blog, I’ll attempt to explain the important pieces of coding your Citrix Provisioning Server client actions.  For the entire C# file, scroll to the bottom. 

Pick the connection:
According to the .config file, there are multiple bindings that Citrix Provisioning Server has available.  For purposes of this document, I chose the WS_HTTP_Binding.   

CommandSetClient client = <span class="code-keyword">new</span> CommandSetClient(<span class="code-quote">"WS_HTTP_Binding"</span>);

Decide the “object type”:

string objectToAdd = <span class="code-quote">"Device"</span>;

Put together the device parameters:
When using the client API, the MCLI commands are organized as a KeyValuePair array.  Imagine the KeyValuePair array in the form of the following table:

Key Value
DeviceName APITestDevice
SiteName Sys2LabSite
CollectionName 2k3 Base
Description This device was created to test the Provisioning API.
DeviceMAC DE-AD-BE-EF-CA-FE

In code..

KeyValuePair&lt;string, string&gt;[] addParameters = {
  <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"DeviceName"</span>,<span class="code-quote">"APITestDevice"</span>),
  <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"SiteName"</span>,<span class="code-quote">"Sys2LabSite"</span>),
  <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"CollectionName"</span>,<span class="code-quote">"2k3 Base"</span>),
  <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"Description"</span>,<span class="code-quote">"This device was created to test the Provisioning API."</span>),
  <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"DeviceMac"</span>,<span class="code-quote">"DE-AD-BE-EF-CA-FE"</span>)
};










Running the command:
Now, run the command.  Note that all of the MCLI.exe commands are available via the client API; their associated methods are listed in the ServiceMain.cs file.  For the Citrix Provisioning Server MCLI.exe Add command, you must run the “ExecuteAdd” method.  For the MCLI.exe Run command, you must run the “ExecuteRun” method. 

The ExecuteAdd method takes the string objectToAdd and the KeyValuePair array addParameters:

<span class="code-keyword">try</span>
{
  string resultID = "";
  client.ExecuteAdd(out resultID, objectToAdd, addParameters);
}
<span class="code-keyword">catch</span> (<span class="code-object">System</span>.ServiceModel.Security.SecurityNegotiationException err)
{
  Console.WriteLine(<span class="code-quote">"Action failed. The error message returned was \"</span><span class="code-quote">" + err.Message + "</span>\"");
}
Console.ReadLine(); 

Putting it all together:

using <span class="code-object">System</span>;
using <span class="code-object">System</span>.Collections.Generic;
using <span class="code-object">System</span>.Linq;
using <span class="code-object">System</span>.Text;
using <span class="code-object">System</span>.Collections.Specialized;
using <span class="code-object">System</span>.ServiceModel;
using <span class="code-object">System</span>.ServiceModel.Channels;
 
namespace SoapServer
{
  class Test
  {
    <span class="code-keyword">static</span> void Main(string[] args)
    {
      <span class="code-comment">//Tell the client to use WS_HTTP_Binding entry as described in the
</span>      <span class="code-comment">//.config file
</span>      CommandSetClient client = <span class="code-keyword">new</span> CommandSetClient(<span class="code-quote">"WS_HTTP_Binding"</span>);
      string addCommand = <span class="code-quote">"Device"</span>;
      KeyValuePair&lt;string, string&gt;[] addParameters = {
        <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"DeviceName"</span>,<span class="code-quote">"APITestDevice"</span>),
        <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"SiteName"</span>,<span class="code-quote">"Sys2LabSite"</span>),
        <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"CollectionName"</span>,<span class="code-quote">"2k3 Base"</span>),
        <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"Description"</span>,<span class="code-quote">"This device was created to test the Provisioning API."</span>),
        <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"DeviceMac"</span>,<span class="code-quote">"DE-AD-BE-EF-CA-FE"</span>)
      };  
      <span class="code-comment">//Display what the user would have to type locally on the Provisioning Server
</span>      <span class="code-comment">//...<span class="code-keyword">for</span> the add device to collection command
</span>      Console.WriteLine(<span class="code-quote">"About to execute the following command(s) on Provisioning Server:\n"</span>);
      string addAction = <span class="code-quote">"ExecuteAdd"</span>;
      addAction = addAction.Replace(<span class="code-quote">"Execute"</span>,"");
      Console.Write(<span class="code-quote">"MCLI.exe "</span> + addAction + <span class="code-quote">" "</span> + addCommand + <span class="code-quote">" /r "</span>);
      <span class="code-keyword">for</span> (<span class="code-object">int</span> i = 0; i &lt; addParameters.Length; i++)
      {
        Console.Write(addParameters[i].Key + <span class="code-quote">"=\"</span><span class="code-quote">" + addParameters[i].Value + "</span>\<span class="code-quote">" "</span>);
      }
 
      <span class="code-comment">//Run the commands using the client methods  
</span>      <span class="code-keyword">try</span>
      {
        string resultID = "";
        client.ExecuteAdd(out resultID, addCommand, addParameters);
      }
      <span class="code-keyword">catch</span> (<span class="code-object">System</span>.ServiceModel.Security.SecurityNegotiationException err)
      {
        Console.WriteLine(<span class="code-quote">"Action failed. The error message returned was \"</span><span class="code-quote">" + err.Message + "</span>\"");
      }
      Console.ReadLine();
    }
  }
}

Add More…
How about adding that newly created device to Active Directory???  All you’d have to do using the Citrix Provisioning Server MCLI.exe utility is type the following:  MCLI.exe Run AddDeviceToDomain /p DeviceName=APITestDevice

However, you can add a few extra lines to the above code in the right places…

string runCommand = <span class="code-quote">"AddDeviceToDomain"</span>;
KeyValuePair&lt;string, string&gt;[] addToDomainParameters = {
  <span class="code-keyword">new</span> KeyValuePair&lt;string,string&gt;(<span class="code-quote">"DeviceName"</span>,<span class="code-quote">"APITestDevice"</span>)
};

  and…

client.ExecuteRun(runCommand, addToDomainParameters);

Extend this script…
Now you have a script that makes the device available in the Citrix Provisioning Server Console, and its associated Active Directory object has also been created.  These are very simple scripts for now, but imagine what this allows you to do…

Initially, you manually setup a Citrix Provisioning Server vDisk for a VM that has a generalized XenApp installation on it. 

Next, you write an automated deployment script that does the following:
1.  Duplicate 50 “Diskless” VM templates within XenServer (Diskless templates are for use with Provisioning Server).
2.  Continue that script by grabbing the MAC address of all the created VMs, and using the above scripts to add the devices to the collection containing the XenApp vDisk template.
3.  Continue that script by adding the devices to Active Directory. 

With the above four steps (including the manual step of creating a XenApp vDisk image), you will have automated the deployment of a 50 server farm simply by launching a script that accesses APIs available from Citrix XenServer, and Citrix Provisioning Server.

…now imagine the implications scripts like these could have on automating not only the deployment, but the day-to-day management of a dynamic, next-generation datacenter using products available from Citrix.

Definitely more to come…
  MikeS
  Citrix Systems, Inc.