PowerShell is getting increasing attention and gaining followers each day. That is a good thing in my book. I saw a tweet about Citrix OctoBlu automation where Dave Brett (@dbretty) was using it to save money with a PowerShell script (full post here) to power on and off VMs. I reached out to him and asked if he would like a little help with his PowerShell script. To my delight, he happily accepted and this post is about how I transformed his scripts to take advantage of the full power of The Shell. Fair warning is in order, since I have never used or touched a OctoBlu solution.
What we would like to change
First of, a PowerShell function should do one thing and do it well. My first goal was to split the function into two parts where we have one function that handles both the startup and the shutdown of the VM-guests. Secondly I would like to move the mail notification out of the function and either put it in a separate function or use the built in cmdlet Send-MailMessage which has been available since PowerShell version 3.0. Nothing wrong with using the .net class, however I like to use cmdlets if they provide similar functionality.
Secondly I changed the function to an advanced function to leverage WhatIf and all the streams (debug, verbose, information etc). I also added some Write-Verbose statements. The difference between a regular function and an advanced function can be as simple as adding [cmdletbinding()] to your function. If you do, you have to use a Param() section to define your parameters.
Third I added parameters to the function. From the scripts I decided to create the following parameters:
- Credential as [PScredential]
- XenServerUrl as [string]
- VMname as [string]
- Shutdown as [switch]
Forth I added Begin, Process and End blocks to enable PipeLineInput for the VMname parameter. Also to take advantage of configuring the requirements like Import-Module and Connect-XenServer in the Begin-block.
Fifth I added an output object to the function in which I output the VMname and the action taken with the VM (startup or shutdown). The reason for that becomes clear when we start to setup notification.
Those are the 5 big changes I have made to the initial scripts. Other than that I added some personal features related to the use of Write-Verbose and other minor stuff.
How to handle credentials
Every time you add a parameter to your function called username or password you should stop and think. You should most likely use a PScredential object instead. So how do you access those credentials at runtime? This script needs credentials and you cannot prompt the OctoBlu automation engine to provide those. Perhaps OctoBlu have a credential store, however I do not know.
An secure and easy solution to this problem is to use the DAPI built-in encrypting API. The same logic can be applied to any service or service automation solutio that need specific credentials to execute your scripts included scheduled tasks. We will leverage tree cmdlets to accomplish this:
First you need to start a PowerShell host as the user that need to use your credentials. Then we need to run these commands:
This will create a PScredential object and the Export-CliXml will protect the password with DAPI when you create the XenCred.xml file. That file can only be decrypted with Import-CliXml running under the account it was created with. So when you need to access those credentials you run:
The updated script
The Shell Thing
(Screenshot of OctoBlu, image by Dave Brett)
Dave Brett uses the profiles.ps1 script to make functions available in OctoBlu. That is fine, however it makes it hard for people that don’t know PowerShell to figure out where the function (Lab-Shutdown) comes from. I would suggest to add something like this in the script box:
This is just a suggestion which in my opinion makes it easier to follow what is happening. Since the Set-LabPowerState and the parameter VMName takes an array of strings, we could take the content of the file holding the names of the VMs and use that. I decided to use a foreach loop for readability reasons.
I probably need to say something about a technique called splatting in PowerShell. Have a look at this line:
Set-LabPowerState @setLabPower -VMname $vm
A few lines up, you can see I create a variable $SetLabPower which is a hashtable. The keys in the hashtable match the name of the parameters of the function Set-LabPowerState. This makes it easier to read when you call functions or cmdlets that have many parameters. We can then provide those keyvalue-pairs to the function using a @ in front of the variable name.
The other thing to note is that I am using dotsourcing to make the Set-LabPowerState function available in the Script Thing session. I am assuming that the content of my new function is saved in the c:\scripts\Set-LabPowerState.ps1 file.
Since my function outputs an object for each VM it processes, we can leverage that in the email notification setting and provide feedback on the VMs we have messed with. The output for the foreach loop is saved in the $results object. We convert this object to a string representation with the Out-String cmdlet and use that string object as the body of the email.
A note about ErrorAction
Since this script needs access to the XenServerPSModule module and you need to connect to an XenServer, I am using ErrorAction Stop on the Import-Module and the Connect-XenServer statements. This will prevent the script to continue if both prerequisites are not met. In addition the user is presented with a nice message explaining what the issue is.
Benefits of the new script
- We have a function that does a single task even if it can start and shutdown VMs.
- The functions accepts parameters so we can reuse it later
- The function is discoverable by the PowerShell help engine since we have added help in the function
- The automation task in OctoBlu is easier to understand. Think of the next guy
- We can execute the function without actually making changes since it is an advanced function and we have implemented ShouldProcess (WhatIf)
- The function outputs an object which we can reuse in the email notification scenario
So the only thing that is needed is someone to test my improved solution on an OctoBlu server. I have no idea if it works or if you think this is a better solution. I think it is.