Scripting a PVS Capacity on Demand Model with XenApp 6.5 Part 3

Hi everyone! Shane O’Neill back again for the next part of our series on automating PVS to provide on demand capacity for your environment. At the end of my previous post, we had just finished querying XenApp to get our current usage ratio for user sessions per server. Based on the result that we received, we were now in a position to take a course of action, that being to either add more servers to a collection if we were being over-utilized or remove servers in the case of underutilization.

We now find ourselves ready to start using the PVS PowerShell snap-in, MCLIPSSnapin. Now, if you are not familiar with this PowerShell snap-in, brace yourself. Being quite proficient in PowerShell, when I was first working out in my mind how to automate this process via script, I thought to myself “Piece of cake”. It wasn’t long before I came to the realization that this was going to be anything but straight forward.

Firstly, you have to manually register the DLL file for the PowerShell snap-in. You would think that this would be handled automatically as part of the console install but that does not seem to be the case.

For 32-bit
%systemroot%\Microsoft.NET\Framework\v2.0.50727\installutil.exe “C:\Program Files\Citrix\Provisioning Services Console\McliPSSnapIn.dll”

For 64-bit
%systemroot%\Microsoft.NET\Framework64\v2.0.50727\installutil.exe “C:\Program Files\Citrix\Provisioning Services Console\McliPSSnapIn.dll”

Secondly and more importantly, commands run from the MCLIPSSnapin do not return PowerShell objects. That means no nice easy to manipulate collections of objects to work with. No object properties to read or modify. Instead, all the commands return a big chunk of text, not the most appealing output to have to work with, but with a bit of patience and some text manipulation, it can be tamed.

Citrix does provide a PDF reference handbook of PowerShell commands which can be found here.

So, with that said, let’s take a look at the logical flow of our process for adding a server to a collection. There are three key pieces of information that you need to be able to pass in to your code. These are the name of the collection where the spare server is coming from, the name of the collection that it is being assigned to and the name of the site in your PVS farm. We are going to assume here that there are available servers in the source collection. For the purpose of example, the source collection is called MySourceCollection, the target collection is MyTargetCollection and the site is MySite. Again, all my code is in C# with the PowerShell embedded but this can all be accomplished via PowerShell alone.

  1. Get the name of a spare server in the source collection.
  2. Get the details of that spare server.
  3. Get the name of a server in the target collection.
  4. Get the vDisk details of that server.
  5. Delete the spare server from the source collection.
  6. Add the spare server to the target collection.
  7. Assign the vDisk to the spare server.
  8. Boot the server.

 

Step 1

We start by getting the name of the spare server in the source collection. The function below has the name of the source collection and the site passed in to it. It will then return the first device in that collection.

string deviceName = getDeviceName(“MySourceCollection”, “MySite”);

static string getDeviceName(string sourceCollection, string siteName)
{
string result = null;
PowerShell ps = PowerShell.Create();
string command = “Add-PSSnapin McliPSSnapin” + Environment.NewLine +
“$results = Mcli-Get Device -p CollectionName=” + sourceCollection + @”, siteName=””” + siteName + @””” -f DeviceName” + Environment.NewLine +
“$results[4]”;
ps.AddScript(command);
var output = ps.Invoke();
result = output.ToString();
int splitPosition = result.IndexOf(“:”);
return result.Substring(splitPosition + 2, result.Length – (splitPosition + 2));
}

Here is the PoSH command for easier reading so that we can break it down:

Add-PSSnapin McliPSSnapin
$results = Mcli-Get Device -p CollectionName=MySourceCollection, siteName=MySite -f DeviceName
$results[4]

The first line loads the require PoSH snap-in. Next, we are using the Mcli-Get cmdlet and we tell it that Device is our type that we are getting information on. Mcli-Get can retrieve information of a large amount of types, these are detailed in the reference PDF. Having told it that we are getting information on devices, we pass two parameters, one for the collection name and the second for the site name. Finally, we can use the –f switch to tell it what field we want returned, in this case the Devicename. When the command is executed, it will return all the names of the devices in that collection. I am assigning those to a variable called results which is basically an array of strings. The reason for then calling $results[4] is that I know the name of the first device will be contained in this slot of the array. It is returned like this:

deviceName: NameOfDevice

That is where the final part of the code comes in to play, the string manipulation. I use an IndexOf function to locate the position of the colon in the string. Once I know the location of that, I can then successfully split the string using a substring function and we are left with the name of the spare device that we can use. Fun, eh? A bit of work involved but once you know where you can predict that the McliPSSnapin will contain the information that you need in its output, things start to click in to place.

Step 2

We have the name of our spare server now. Next up, we need to get some additional information about that device. There are 8 properties of it that we need to record. These will be required when we go to add it to the target collection. In this example, I am using a class called deviceDetails to capture that information. You can do this as you prefer so long as you end up with an object that has 8 string properties.

static deviceDetails getDeviceDetails(string deviceName)
{
PowerShell ps = PowerShell.Create();
string command = “Add-PSSnapin McliPSSnapin” + Environment.NewLine +
“Mcli-Get Device -p DeviceName=” + deviceName + ” -f deviceMac, adtimestamp, adsignature, domainname, domainobjectsid, domaincontrollername, domaintimecreated, description”;
ps.AddScript(command);
var output = ps.Invoke();
deviceDetails details = new deviceDetails();
details.deviceMAC = formatResultString(output[4].ToString());
details.deviceADTimeStamp = formatResultString(output[5].ToString());
details.deviceADSignature = formatResultString(output[6].ToString());
details.deviceDomainName = formatResultString(output[7].ToString());
details.deviceObjectSID = formatResultString(output[8].ToString());
details.deviceDomainControllerName = formatResultString(output[9].ToString());
details.deviceDomainTimeCreated = formatResultString(output[10].ToString());
details.description = formatResultString(output[11].ToString());
return details;
}

PowerShell code:

Add-PSSnapin McliPSSnapin
Mcli-Get Device -p DeviceName=MyDeviceName -f deviceMac, adtimestamp, adsignature, domainname, domainobjectsid, domaincontrollername, domaintimecreated, description

Again, we are using the Mcli-Get command and specifying a type of Device. We are able to search by the device name and I am using the –f switch to only return the fields that we need to capture. Once more, by knowing where the information that we want to capture in the output is located, we can extract it. You’ll notice that I am using a function now format the string just like we did in the first piece of code.

static string formatResultString(string input)
{
int splitPosition = input.IndexOf(“:”);
return input.Substring(splitPosition + 2, input.Length – (splitPosition + 2));
}

We are left with an object that contains the information about the spare server that we will require at a later stage.

Step 3

The third step in our logical flow is to get the name of a server in the target collection. I don’t need to post code for this as it is the exact same as that which was used to get the name of the spare server in the source collection, you just need to pass in the appropriate collection name.

Step 4

Having captured the name of a device in the target collection, our fourth step is to get the vDisk information for that server. We need this so that we can assign the correct vDisk to the spare server when we have added it to the target collection.

static string getvDisk(string deviceName)
{
string result = null;
PowerShell ps = PowerShell.Create();
string command = “Add-PSSnapin McliPSSnapin” + Environment.NewLine +
$diskInfo = Mcli-Get diskinfo -p deviceName=” + deviceName + Environment.NewLine +
“$diskinfo[4]”;
ps.AddScript(command);
var output = ps.Invoke();
result = output.ToString();
ps.Dispose();
return formatResultString(result);
}

PowerShell code:

Add-PSSnapin McliPSSnapin
$diskInfo = Mcli-Get diskinfo -p deviceName=MyDeviceName –f diskLocatorId
$diskinfo[4]

This time when we are using the Mcli-Get cmdlet, we are passing a different type in to it, DiskInfo. As a result, the command will get the disk info for the device name that we are passing to it. Once more, I am using the –f switch to only return the property that we need which is the vDisk Locator ID. We have now gathered all the information that we need to perform the function that this has all been leading up to, adding the spare server to our target collection.

Step 5

Before we can add the spare server to the target collection, we must first remove it from the source collection. Remember that object that we created back in step 2? Well, here is our first use for it. We are going to use the MAC address that it recorded for the device in order to delete it from the collection. We are using the Mcli-Delete cmdlet and specifying a type of Device.

static bool deleteDevice(string deviceMac)
{
PowerShell ps = PowerShell.Create();
string command = “Add-PSSnapin McliPSSnapin” + Environment.NewLine +
“Mcli-Delete Device -p devicemac=” + deviceMac;
ps.AddScript(command);
var output = ps.Invoke();
if (output[2].ToString().ToUpper().Trim() == “DELETE SUCCEEDED.”)
{
return true;
}
else
{
return false;
}
}

PowerShell code:

Add-PSSnapin McliPSSnapin
Mcli-Delete Device -p devicemac=MyDeviceMAC

If after issuing the command, we do not find that exact text in that exact location, we know that the command has failed and the device has not been deleted. When the command succeeds, you will find the text and we know then that we can proceed with our next step, adding the device to the target collection.

Step 6

We can now add our spare device to the target collection. In the below code, I am showing how I first call the method to add the device. As you will see, I am passing in a lot of the information that we have had to gather up to this point. This include the spare device name, the target collection, the site and the 8 properites of the spare device that we have captured in step 3.

The PowerShell in the add method is using the Mcli-Add cmdlet in to which we are then passing all 11 pieces of information. These are all required by it to add the device to the collection. Once more, after invoking the command, the resulting output is highly predictable. If it succeeds, we will always find that exact text in that exact location. Anything else means the attempt to add the device to the collection has failed.

addDevice(spareDeviceName, dDetails.deviceMAC, “MyTargetCollection”, “MySite”, dDetails.deviceADTimeStamp, dDetails.deviceADSignature, dDetails.deviceDomainName, dDetails.deviceObjectSID, dDetails.deviceDomainControllerName, dDetails.deviceDomainTimeCreated, dDetails.description)

static bool addDevice(string devicename, string devicemac, string collection, string site, string adtimestamp, string adsignature, string domainname, string domainobjectsid, string domaincontrollername, string domaintimecreated, string description)
{
PowerShell ps = PowerShell.Create();
string command = “Add-PSSnapin McliPSSnapin” + Environment.NewLine +
“Mcli-Add Device -r devicename=” + devicename + “, devicemac=” + devicemac + “, collectionname=” + collection + @”, sitename=””” + site + @”””, adtimestamp=” + adtimestamp + “, adsignature=” + adsignature + “, domainname=” + domainname + “, domainobjectsid=” + domainobjectsid + “, domaincontrollername=” + domaincontrollername + @”, domaintimecreated=””” + domaintimecreated + @”””,description=””” + description + @””””;
ps.AddScript(command);
var output = ps.Invoke();
if (output[2].ToString().Substring(0, 14).ToUpper().Trim() == “ADD SUCCEEDED.”)
{
return true;
}
else
{
return false;
}
}

PowerShell code:

Add-PSSnapin McliPSSnapin
Mcli-Add Device -r devicename=devicename, devicemac=devicemac, collectionname=MyTargetCollection, sitename=MySite, adtimestamp=adtimestamp, adsignature=adsignature, domainname=domainname, domainobjectsid=domainobjectsid, domaincontrollername=domaincontrollername,domaintimecreated=domaintimecreated,description=description;

Step 7

The spare server has now been removed from the source collection and added to the target collection. We now need to assign it the vDisk of that target collection which we gathered in step 4. Hopefully by this stage, you can see how each of the functions that we have used are very similar in structure. For this command, we are using the Mcli-Run cmdlet and specifying a type of AssignDiskLocator. The device is identified by its MAC address and we also pass in the vDisk Locator ID. Once more, we are able to tell when the command succeeds based on locating text in the output.

static bool assignVDiskToServer(string deviceMac, string vdisk)
{
PowerShell ps = PowerShell.Create();
string command = “Add-PSSnapin McliPSSnapin” + Environment.NewLine +
“Mcli-Run AssignDiskLocator -p deviceMac=” + deviceMac + “, diskLocatorId=” + vdisk;
ps.AddScript(command);
var output = ps.Invoke();
if (output[2].ToString().Substring(0, 14).ToUpper().Trim() == “RUN SUCCEEDED.”)
{
return true;
}
else
{
return false;
}
}
PowerShell code:

Add-PSSnapin McliPSSnapin
Mcli-Run AssignDiskLocator -p deviceMac=deviceMac, diskLocatorId=vdisk

Step 8

Wow! Are we here already? We have successfully remove a spare device from a source collection, added it to a target collection and assigned a vDisk to it. All that is left is to boot up the device. There is a cmdlet in McliPSSnapin that can do this but I have found it to be unreliable. The cmdlet to shutdown a device does seem to work well but for powering on a device, I would suggest that you use the PowerShell SDK for your particular hypervisor. Just for reference, these are the boot and shutdown cmdlets. You can pass in the device MAC as the parameter to identify the target.

Boot: Mcli-RunWithReturn Boot
Shutdown: Mcli-Run Shutdown

As I mentioned in my previous post, the above code is not complete. Proper error handling and validation should be included in it, I just wanted to keep it as concise as possible so that I could get across the overall idea and flow of the process. Hopefully, you have found this useful.

Questions or comments, feel free to contact me. Email: sasponto(AT)gmail(DOT)com Twitter: @SasPonto

, ,

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">