Often you would like to customise one or two things with a script in one of the predefined workflow steps like ‘BuildingMachine’. However depending on your blueprint you do not neccessarily want to execute all of the scripts all time. Having some more dynamic selection would come in handy. This can be easily done with vCAC’s built in properties features. Here is how:

In order to have the WFStubs being run during provisioning you have to first define the respective property like ‘ExternalWFStubs.BuildingMachine’. Then depending on your need you can then change that stub workflow to either call a vCO workflow (that can easily be invoked via the new vCO plugin as described in a previous post) or you can invoke a PowerShell script directly (or of course you can do whatever you like within the workflow with one of the predefined Workflow Foundation Activities).
Maybe the easiest way is just to call a PowerShell script wrapper from either a script that you uploaded within vCAC or that you invoke from a known place like ‘C:\scripts’ (there you have to make sure that this gets replicated to all your DEM workers).
Then you have to specify what script or Cmdlet you actually want to run by creating new properties tht you link to a blueprint either directly or via a build profile like you can see in the following figure:

Property Definitions for executing custom scripts
Property Definitions for executing custom scripts

The wrapper script then only needs to look up the properties, construct the command line and execute the scripts. To make the whole process a little bit more flexible you can define scripts for each stage during the provisioning process (eg renaming a virtual machine does only make sense during a BuildingMachine step). In addition to defining the scripts to run you can also specify parameters as you can see in the previous figure. There you can also reference other properties or attributes of the Machine object by defining something like this: ‘#Machine.VMCreationDate’ or ‘#Machine.VMCPUs’ or any other custom property with ‘#Properties.’biz.dfch.PS.vCAC.OrderInformation.CostCenter’.

All the scripts will be checked for existence and ordered in alphabetical order. So it is best to prepend the script keys with a naming convention like ’01’, ’02’, ’03’, …

So in the example we have a script ‘Set-DomainMembership.ps1’ that accepts three parameters: ‘Domain’, ‘Username’ and ‘Password’. We use the requesting user of the machine as the ‘Username’ by specifying ‘#Machine.Owner.Username’. The password is a regular encrypted vCAC property that will be passed to the ‘Set-DomainMembership.ps1’ in an unencrypted form (check this post to get more information about encrypted properties in vCAC). The ‘Domain’ parameter is hard-coded set to ‘sharedop.org’.

The last example shows you how to specify another existing custom property that the script ‘Set-VcacVirtualMachineName.ps1’ expects as its ‘Name’ parameter: ‘biz.dfch.Machine.NewName’ is another property we defined on the blueprint that the user can either enter directly or that could be computed by some previously run scripts.

Side note: you can actually define any valid powershell expression as long as it starts with a ‘$’. This is then evaluated at run time. While this is quite powerful this is also a little bit risky. And you would certainly not let your end users or customers define these expressions.

The script actually consists of two parts: first the logic to enumerate all custom properties of relevance in the current VirtualMachineState and second to construct the command line.


# https://d-fens.ch/2013/12/05/vcac-dynamically-execute-scripts-in-externalwfstubs-workflows-with-powershell/
PS > $Machine.GetType().FullName
DynamicOps.ManagementModel.VirtualMachine
PS > $m.GetType().FullName
DynamicOps.ManagementModel.ManagementModelEntities
# script main
$Write-Host ("{0}: Processing Machine entity: '{1}'." -f $Machine.VirtualMachineID, $Machine.VirtualMachineName);
$null = $m.LoadProperty($Machine, 'VirtualMachineProperties');
$hp = @{};
$m.LoadProperty($Machine, 'VirtualMachineProperties') | Select-Object PropertyName, PropertyValue, IsEncrypted |% {
if($_.IsEncrypted) {
$hp.Add($_.PropertyName, [DynamicOps.Common.Utils.ScramblerHelpers]::Unscramble($_.PropertyValue));
} else {
$hp.Add($_.PropertyName, $_.PropertyValue);
} # if
};
# Get all properties for current machine state
$WorkflowStubIdentifier = 'ExternalWFStubs';
$ServerScripts = 'Scripts';
if($PSBoundParameters.ContainsKey('VirtualMachineState')) {
$VirtualMachineState = $Machine.VirtualMachineState;
} # if
$ap = $hp.Keys -match ('^{0}\.{1}\.{2}\.' -f
$WorkflowStubIdentifier, $VirtualMachineState, $ServerScripts) | Sort;
$al = New-Object System.Collections.ArrayList;
$PropertyScript = '';
$ScriptName = '';
foreach($p in $ap) {
# Get script name
$fMatch = $p -match ('^{0}\.{1}\.{2}\.({3}|{3}\.{4})$' -f
$WorkflowStubIdentifier, $VirtualMachineState, $ServerScripts, '([^\.]+)', '([^\.]+)');
if(!$fMatch) { continue; }
if($Matches.Count -eq 3) {
if($PropertyScript -And $ScriptName) {
Write-Host $PropertyScript
$ScriptCommand = New-ScriptCommandline
-Machine $Machine
-ScriptName $ScriptName
-htScriptParam $htScriptParam
-Path $Path;
if(!$ScriptCommand) {
Write-Warning ("'{0}': '{1}' FAILED to extract command line." -f
$PropertyScript, $ScriptName);
} # if
$null = $al.Add($ScriptCommand);
} # if
$PropertyScript = $Matches[2];
$ScriptName = $hp.$p;
$htScriptParam = @{};
} elseif($Matches.Count -eq 4) {
$PropertyParam = $Matches[4];
$ScriptParam = $hp.$p;
$htScriptParam.$PropertyParam = $ScriptParam;
} else {
Write-Warning ("{0}: Matches.Count – continue" -f $p);
continue;
} # if
} # foreach
if($PropertyScript -And $ScriptName) {
Write-Host $PropertyScript
$ScriptCommand = New-ScriptCommandline
-Machine $Machine
-ScriptName $ScriptName
-htScriptParam $htScriptParam
-Path $Path;
if(!$ScriptCommand) {
Write-Warning ("'{0}': '{1}' FAILED to extract command line." -f
$PropertyScript, $ScriptName);
} # if
$null = $al.Add($ScriptCommand);
} # if
<#
# This is an example output from the above generated array $al
# all scripts are constructed from the properties attached to the virtual machine (including credentials)
C:\data\scripts\Set-DomainMembersip.ps1 -Username 'SHAREDOP\myUser' -Domain 'sharedop.org' -Passwod 'P@ssw0rd'
C:\data\scripts\New-CmdbEntry.ps1 -Machine 'vc100228' -Username 'SHAREDOP\Edgar' -Password 'P@ssw0rd'
C:\data\scripts\Set-VcacVirtualMachineName.ps1 -Name 'myNewVMName' -Fqdn 'example.com'
#
#>

See Gist at https://gist.github.com/dfch/52c5914a411467cb2852

In the second part we actually construct the command line to execute with all parameters defined by the various properties. We also check if the specified parameters are valid to the actual command. If not the respective parameter is skipped. The default directory where to look for the scripts is the directory where the main script is run from.


# https://d-fens.ch/2013/12/05/vcac-dynamically-execute-scripts-in-externalwfstubs-workflows-with-powershell/
# Machine is the currently processed virtual machine
# Path is a default path where the scripts are located
# ScriptName is the name of the PowerShell script passed from the custom properties
# htScriptParam is a hastable containing parameter name and raw expression
$ScriptName = Join-Path -Path $Path -ChildPath $ScriptName;
if( !(Test-Path -Path $ScriptName) ) {
Write-Error ("ScriptName '{0}' does not exist." -f $ScriptName);
return $null;
} # if
$Cmd = Get-Command $ScriptName;
$ScriptParams = '';
foreach($k in $htScriptParam.Keys) {
if(!$Cmd.Parameters.ContainsKey($k)) {
Write-Error ("'{0}' does not contain specified parameter '{1}'. Skipping …" -f
$ScriptName, $k);
continue;
} # if
$ScriptParam = $htScriptParam.$k;
$ScriptParam = $ScriptParam.Replace('#Machine', '$Machine');
if($ScriptParam -match "#Properties\.'([^\']+)'") {
$p = $Machine.VirtualMachineProperties |? PropertyName -eq $Matches[1];
if(!$p) {
Write-Error ("Resolving '{0}' with ScriptParam '{1}' [{2}] FAILED." -f
$ScriptName, $k, $ScriptParam);
continue;
} # if
$ScriptParam = $p.PropertyValue;
} # if
if($ScriptParam.StartsWith('$')) {
Write-Debug ("ScriptParam in Invoke-Expression: '{0}'." -f $ScriptParam)
$ScriptParam = Invoke-Expression -Command $ScriptParam;
} # if
$ScriptParams = "{0} -{1} '{2}'" -f $ScriptParams, $k, $ScriptParam;
} # foreach
$ScriptCommandline = "{0}{1}" -f $ScriptName, $ScriptParams;
PS > $ScriptCommandline;
C:\data\scripts\Set-VcacVirtualMachineName.ps1 -Name 'myNewVMName' -Fqdn 'example.com'

See Gist at https://gist.github.com/dfch/3a6d9fb037896c0c2c41

As usual error handling and logging has been reduced to a minimum to maintain readability.

11 Comments »

  1. Thanks for sharing the script, can I have powershell script without password in it ? I see that you are passing from build profile, can I use it without password ? Does it work on VCAC 5.2 ??

    • I am not sure if I understood your question correctly. To be able to join a machine to a domain you need appropriate permissions. And as the machine to be joined has only local accounts you would need an account with the same name as the vcac service account as well. The it could work without using username and password. And yes, it works with vcac 5.2. in case i did not understand your question correctly or there is still something unclear please let me know. Regards, Ronald

      • Thanks Ronald for your reply. I’m little confused here. I have VCAC integrated with VCO for workflows, hope the above procedure works. I see that you have already given the username passwords in the custom properties, I know the domain join script uses the credentials, will it not use the values you provided in custom property of the blueprint? I am still not clear for giving same credentials in the same script. I am looking for the powershell script to join the domain.Can you please share the script “Set-DomainMembership.ps1” without credential? It must be using the credentials given in the blueprint custom properties.

        C:\data\scripts\Set-DomainMembersip.ps1 -Username ‘SHAREDOP\myUser’ -Domain ‘sharedop.org’ -Passwod ‘P!ssw0rd’

      • Hi Rama, the scripts do actually take the credentials from the template, it is not hard-coded in the script. Are you referring to the last lines in the first sourcecode block “$al … Set-DomainMembership.ps1”? This was just for illustrational purpose to show the expanded scripts that would be executed. I will put them into a comment block to clarify this. One thing to note: you wrote you set up vCO integration. So would you normally then execute your scripts from within the vCO workflow and not from the vCAC workflow itself. You would instead use the vCO object model representation of vCAC an read the properties (though both ways work). I will post the the Set-DomainMEmbership.ps1 as well.
        Hope things are now a bit clearer. Otherwise leave a note so we can shed some additional light to it.

  2. Hi Ronald,
    Thank you very much for sharing the script, Yes, I do have VCO for calling the stubs. However, I don’t have powershell host to execute the powershell scripts. Can I still use “Scriptable task” in VCO without PowerShell Host? That would be the best option. Do I need to copy/paste this script? this is going to be my first scriptable task in VCO then.

    If not, I can use blueprint custom properties and copy the script on DEM workers (C:\Scripts), will that work?

    • Hi Rama, you can still use “Scriptable Tasks” even without the PowerShell host. You could use an action that executes a process (PowerShell.exe, that in turn would run your script). However, this only works, when you have vCO installed on a Windows box (and not the appliance). In general, “Scriptable Tasks” in vCO can only contain Javascript code. So if this really your first vCO scriptable task, this might be a rather complex task for a start. I really suggest you have a look at the VMware forums for vCO and look for examples on how to write and execute scriptable tasks in general and how to invoke PowerShell. There are plenty of examples also on the Internet. Once you have mastered to run PowerShell scripts from vCO you could start tackling the vCAC integration. But I would strongly suggest to approach this stepwise.
      If you do not want the full vCO integration you can stick to the example in my post and run the PowerShell script directly on the DEM workers (but you have to copy the script to all and every DEM worker instance manually before you run it). This will leave vCO out of the game and might make it easier to achieve what you want.
      Good luck! Ronald

      • Thank you very much for your inputs. For time being, I will try your instructions on VCAC

  3. Ronald,

    I’m getting the below error when I tried to use it through VCAC. Looks like the decryption is not working. Did you notice it earlier?

    Workflow “WFStubBuildingMachine” failed with the following exception:
    System.InvalidOperationException: Failed to load configuration section encryptionKey in configuration file C:\Program Files (x86)\VMware\vCAC\Distributed Execution Manager\DEM_WORKER\DynamicOps.DEM.exe.Config

    • Hi Rama, when and in what stage does the error occur? What operation did you perform? I just checked the config file on a worker from my system and I do not have an “encryptionKey” section in my DEM worker config. Besides, I have never had this error before, either.

      • This error is from DEM worker log. Server build fails and getting disposed. Do I need to add decrypt script for VCAC password which is encrypted in custom properties?

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.