Simplify your life while testing and debugging PowerShell scripts in vCAC

You have read all the blogs like Calling Powershell/PowerCLI Scripts from a vCAC workflow on how to call PowerShell scripts from vCAC or -if you really have to- vCO (when using the new vCAC plugin for vCO)? And you think it’s a cool feature? However, you have a bad feeling when it comes to testing as you cannot debug them in your favourite IDE (eg Windows PowerShell ISE) but instead have to fire and forget them (something a vCO programmer has got used to it over the years)? Don’t – there is help.
Have your scripts be “real” scripts instead of just script blocks as described in Tom’s blog and make them accept an input parameter for the management context as well as for the virtual machine (besides in object format also in guid/id format so you can easily call them from vCO). With that you can setup the call to the script like this:

vCAC PowerShell Parameters

vCAC PowerShell Parameters

As you can see there is no need to specify parameters with their exact types; [Object] is totally sufficient. And you also notice that we do not pass down any properties, as the machine object already contains everything we need (for readonly we keep them in a hashtable ‘$hp’).
Then you define the input parameters of your script as follows:

Param (
  [Parameter(Mandatory = $false, Position = 0)]
  $MgmtContext = $biz_dfch_PS_vCAC_Utilities.MgmtContext
  [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'id')]
  [string] $MachineId
  [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'o')]
  [DynamicOps.ManagementModel.VirtualMachine] $Machine
  [Parameter(Mandatory = $true)]
  [string] $Name
  [Parameter(Mandatory = $false)]
  [string] $someOtherParam = "Peter Lustig"
) # Param

If you do not use our vCAC PowerShell module you would define the first parameter as ‘mandatory=$true’ and not specify a default value. You then just have to pass the parameter explicitly when you call this script interactively.
Within the script you resolve the machine id to a machine object if needed and check for our custom “debug” properties:

# Optional: validate MachineId by converting it to guid first
# [guid] $guid = $MachineId;
if($MachineId) {
  $Machine = $MgmtContext.VirtualMachines |? VirtualMachineId -eq $MachineId;
} # if
# Load all properties on VirtualMachine object
($Machine|gm -Type Property).Name |% { $null = $m.LoadProperty($Machine, $_); }
# Convert linked VirtualMachineProperties to a hastable
$hp = @{};
$Machine.VirtualMachineProperties | Select-Object PropertyName, PropertyValue |%
  { $hp.Add($_.PropertyName, $_.PropertyValue); };

# Build the property
$DebugGlobal = 'biz.dfch.PS.vCAC.ExternalWFStubs.Debug';
$DebugCurrent = 'biz.dfch.PS.vCAC.ExternalWFStubs.{0}.Debug' -f $Machine.VirtualMachineState;
if( ($hp.ContainsKey($DebugGlobal) -And ($hp.$DebugGlobal -eq 'True'))
  -Or ($hp.ContainsKey($DebugCurrent) -And ($hp.$DebugCurrent -eq 'True')) ) {
  $DebugWaitFile = '{0}-{1}' -f $PSCommandPath, $Machine.VirtualMachineID;
  Log-Debug $fn ("[DEBUGWAIT] Waiting while '{0}' exists ...`r`n'{0}'" -f $DebugWaitFile);
  Start-Sleep -Seconds 30;
  # Hold while file exists
  while(Test-Path -Path $DebugWaitFile) {
    Log-Debug $fn ("[DEBUGWAIT] Waiting while '{0}' exists ..." -f $DebugWaitFile);
    Start-Sleep -Seconds 30;
} # if

Both aforementioned properties are just plain properties you can set on the blueprint, during the machine request, in a build profile or some where else. Whereever you place the properties if ”biz.dfch.PS.vCAC.ExternalWFStubs.Debug’ is set to ‘True’ the Stub scripts will wait in all provisioning steps. If you only want to wait in certain step you just set the ‘biz.dfch.PS.vCAC.ExternalWFStubs.BuildingMachine.Debug’ property to ‘True’. When the scripts waits it will look for the the file in the same directory where the script resides appended with the virtual machine guid, eg: ‘C:\data\scripts\WFExternalStubs.BuildingMachine.ps1-56d6c853-f468-4c18-811e-749345106dc5’. When you create that file within 30s the script will wait until you delete it (or vCAC times out your script).

With your vCAC script waiting you can now either start another instance of that script in your IDE and debug it step by step getting the same results as if it executed non-interactively.
I really like vCAC for its flexibility …

Footnote 1: you do not have to use our logging module. Just would then need to remove the ‘Log-*’ calls.

Footnote 2: When working with more than one DEM Worker if might be tricky to find out where your script is actually executed. You might want to narrow it down by using skills on the DEM workers and workflows.

[UPDATE 2013-11-17]
Instead of setting a file on the DEM worker where script is being executed an easier approach would be to actually set a flag in a more central location – as in vCAC directly. Custom Properties can help here to. So instead of the script checking for a file in ‘$PSCommandPath’ you might check for a custom property called ‘WFExternalStubsMachineBuilding.ps1-855b4ddf-1292-47fa-9523-06eeae577292’. This can be easily achieved like this when using our ‘biz.dfch.PS.vCAC.Utilities’ module:

$DebugWaitProperty = '{0}-{1}' -f $PSCommandPath, $Machine.VirtualMachineID;
do {
  Log-Debug $fn ("{0}: [DEBUGWAIT] Waiting while property '{1}' exists ..." -f $Machine.VirtualMachineID, $DebugWaitProperty);
  Start-Sleep -Seconds 30;
  $fContinue = $false;
  try { $fContinue = Get-VcacPropertyDefinition -Name $DebugWaitProperty -Expand None -As json; }
  catch { }
} while($fContinue);

And here is how you can quickly check for the newly created VM and create a property without waiting to look at the DEM worker log:

PS > $m.VirtualMachines | Select-Object VirtualMachineName, VirtualMachineID 
  |? VirtualMachineName -Match '^vc1'

VirtualMachineName VirtualMachineID
------------------ ----------------
vc100185           2f8f4113-f51a-4a0b-862d-a2e17e6225ba
vc100184           8fc3e9de-d991-4afd-a7c3-bf8c64fef37d
vc100186           fd7d1b65-a1ae-4c2e-b823-eea4dd502b07

PS > New-VcacPropertyDefinition -Confirm:$false -Type TextBox -Name 
  ('C:\data\scripts\WFExternalStubs.MachineBuilding.ps1-{0}' -f 'fd7d1b65-a1ae-4c2e-b823-eea4dd502b07')

Descriptor                        Headers StatusCode                Error
----------                        ------- ----------                -----
System.Data.Services.Client.En... {[DataServiceVersion, 1.0;], [... 201

[UPDATE] Another way to get the most recently created VM:

PS > $m.VirtualMachines | 
  Select-Object VMCreationDate, VirtualMachineID, VirtualMachineName | 
  Sort -Property VMCreationDate -Descending | 
  Select-Object -First 1;
VMCreationDate     VirtualMachineID                     VirtualMachineName
--------------     ----------------                     ------------------
11/18/2013 3:06:42 1de1bd9d-345c-46b9-bb2f-8bb0f7edf63e vc100229


  1. […] This code can be placed into a PowerShell script that is called during the BuildingMachine step as described in Simplify your life while testing and debugging PowerShell scripts in vCAC. […]

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: