Using Puppet to join Windows guests to a domain with vCloud Director

You may be aware that you can join Windows VMs to a domain using the built-in functionality within vCloud Director.  However, this requires that a DHCP server be available on the network that the VMs are connecting to.  This can sometimes be a pain if you’re spinning multiple networks and have to engage with another team to set up a DHCP server.  In this post I’ll show how to use Puppet to join vCloud VMs to a domain.

Software versions used

  • Puppet 3.2
  • vCloud 5.1.1
  • vSphere 5.5
  • Windows 2012 R2

Puppet Server Configuration

I’m not going to cover how to install the Puppet server.  You can read how to install it here:

We need to install two Puppet modules: domain_membership and reboot.  Reboot is needed to reboot the guest after it has been joined to the domain.

Here is a video on how to use the reboot module.

site.pp changes


Here I edit site.pp so that when my vCloud VMs (default names of win2012-###) contact the puppet server, the Puppet server will run the domain_membership module.   Read the domain_membership link above for all of the module’s options.

node /^win2012.*\.vmware\.local$/ {
   class { 'domain_membership':
   domain   => 'vmware.local',
   username => 'chris',
   password => 'password',
   force    => true,
   resetpw  => false,

domain_membership module’s init.pp file


You can leave most of this file alone, but we need to add the reboot module info.

I did change the join option from a value of 32 to 39.  You can read all of the options here and enter what makes the most sense in your environment.  If you want more flexibility, you can edit the code so it will build up the value depending on what parameters you pass in.

# 1 (0x1)   Default. Joins a computer to a domain. If this value is not specified, the join is a computer to a workgroup.
 if $force {
 $fjoinoption = '39' # I changed this from the default of 32.
 } else{
 $fjoinoption = '1'

Here are the commands that add the guest to the domain.  All of these are defaults except the notify entry.  The notify entry says to call the Reboot module once the guest has been joined to the domain.

# Since the powershell command is combersome, we'll construct it here for clarity... well, almost clarity
$command = "(Get-WmiObject -Class Win32_ComputerSystem).JoinDomainOrWorkGroup('${domain}',${_password},'${username}@${domain}',${_machine_ou},${fjoinoption})" 

exec { 'join_domain':
  command  => $command,
  unless  => "if((Get-WmiObject -Class Win32_ComputerSystem).domain -ne '${domain}'){ exit 1 }",
  provider => powershell,
  notify   => Reboot['after']

Add the reboot module by adding these lines to the end of the class.

reboot { 'after':
   apply => finished,

Puppet Agent Configuration

On the Windows 2012 R2 VM that is going to be your vCloud template, download and install the Puppet agent

$url = “”

Invoke-WebRequest -Uri $url -OutFile puppet.msi

cmd /c “msiexec /qn /i puppet.msi /l*v install.log”

Verify the Puppet agent version

& ‘C:\Program Files (x86)\Puppet Labs\Puppet\bin\puppet.bat’ –version

Add the Puppet agent to the PATH environment variable

set PATH=%PATH%;C:\Program Files (x86)\Puppet Labs\Puppet\bin

The Puppet agent configuration file (puppet.conf) is located in puppet.conf is in %PROGRAMDATA%\PuppetLabs\.  By default, it looks to a server named puppet, which is what my server is named.  You may need to change this.


Make sure that your vCloud template has “Enable  Guest Customization” and “Change SID” enabled under the VM’s Guest Customization tab.  If everything is set up properly, the following steps will happen when you power on a VM deployed from the template:

  1. vCloud will power the VM on in vCenter
  2. When it first boots it will have the templates original name and IP settings.  If the puppet agent service is configured to automatically start and the VM can talk to the Puppet server, the VM will try to its configuration from the Puppet server. Later I’ll discuss this further.
  3. Sysprep will run and the VM will reboot.
  4. When the VM boots back up vCloud will configure the name and IP.
  5. The VM will talk to the Puppet server, run the domain_membership module, which will join it to the domain and then the reboot module will reboot the VM.
  6. After the VM reboots it should be joined to the domain.

In step #2 I mentioned that when the VM first boots and has yet to be sysprep’d or customized (depending on your settings) it’s possible for the VM to be on the network using the templates name/IP and try to talk to the Puppet server.  You probably don’t want this happening so here are a few ways to avoid this.

  • Give the template a name that’s won’t be identified as a node on the Puppet server.
  • Have the Puppet agent service set to manual and then use vCloud’s post guest customization set the service to automatic and start the service.  Once the service is running it will attempt to talk to the Puppet server.
  • Have vCloud’s post customization install the Puppet agent.

Here are some examples using vCloud’s customization script options.  This can be found towards the bottom of the VM’s “Guest Customization” tab within vCloud.

Start the Puppet agent after guest customization

if "%1%" == "precustomization" (
  goto end
) else if "%1%" == "postcustomization" (
  net puppet start

Set the Puppet agent service to automatic and start the Puppet agent after guest customization

if "%1%" == "precustomization" (
  goto end
) else if "%1%" == "postcustomization" (
  sc config puppet start= auto 
  net puppet start


  • I’m a Puppet novice.
  • Location of sysprep log files
  • If sysprep is failing, c:\Windows\Debug\NetSetup.log is a good place to look.
  • If you’ve sysprep’d more than three times during your testing, you can reset it by following these instructions.
  • Location of the vCloud customization log has changed to c:\windows\temp\vmware-imc\guestcust.log
  • Testing was done with UAC turned off.

Using PowerCLI and ovftool to move VMs between vCenters

In this post I’ll show how to use PowerCLI and ovftool to move a VM between two vCenter servers. You can use multiple methods to export a VM from vCenter to a temporary location and then import it into a vCenter from said location, but with this method there is no temporary location used.The network traffic used to transfer the VM goes through the client where the script is ran so you probably want to run it from a location that makes sense. The traffic will go from Source vCenter > where the script is ran > Destination vCenter.The script below is just a listing of commands, you’ll probably want to wrap some of it in functions and add some error handling.

# Configuration
$ovftool = "ovftool.exe"  # If the ovftool.exe is not in your path, you need to specify the full path here.
$sourceVM = 'ovftest'
$sourceVIServer = 'vCenter1.vmware.local'
$targetVIServer = 'vCenter2.vmware.local'
$targetDatacenter = 'Super Datacenter'
$sourceNetwork = 'VM Network'  # This is the portgroup that the VM is currently on.
$targetNetwork = 'VLAN188'  # This is the portgroup that you want the VM placed onto.
$targetCluster = 'Super Cluster'
$targetDatastore = 'VM_Template_Transfer'

# Connect to the source and destination vCenters.
Connect-VIServer $sourceVIServer
Connect-VIServer $targetVIServer

# Assign the vCenters in a lookup table to make things easy to access.
$VIServers = @{
  $DefaultVIServers[0].name = $DefaultVIServers[0];
  $DefaultVIServers[1].name = $DefaultVIServers[1]

# Get the moref of the source VM
$sourceVMMoref = (get-vm $sourceVM -Server $VIServers[$sourceVIServer]).extensiondata.moref.value

# Get a session ticket for the source and destination vCenter servers.  This is what allows us to specify a vCenter server as the source and destination with the ovftool.
echo "sourceVIServer = $($VIServers.$sourceVIServer)"
$sourceSession = Get-View -server $VIServers.$sourceVIServer -Id sessionmanager
$sourceTicket = $sourceSession .AcquireCloneTicket()

echo "targetVIServer = $($VIServers.$targetVIServer)"
$targetSession = Get-View -server $VIServers.$targetVIServer -Id sessionmanager
$targetTicket = $targetSession .AcquireCloneTicket()

# Build the parameters that will be used with the ovftool.
$sourceTicket = "--I:sourceSessionTicket=$($sourceTicket)"
$targetTicket = "--I:targetSessionTicket=$($targetTicket)"
$datastore = "--datastore=$($targetDatastore)"
$network = "--net:$($sourceNetwork)=$($targetNetwork)"
$source = "vi://$($sourceVIServer)?moref=vim.VirtualMachine:$($sourceVMMoref)"
$destination = "vi://$($targetVIServer)/$($targetDatacenter)/host/$($targetCluster)/"

# Display the command that will be ran.
echo $datastore $network $sourceTicket $targetTicket $source $destination

# Use PowerCLI to run the ovftool.  PowerCLI makes it easy to run commands with multiple parameters.  Sometimes this can be tricky to do with Windows.
& $ovftool $datastore $network $sourceTicket $targetTicket $source $destination

Finding expired vCloud vApps with the PowerCLI Cloud-Search commandlet

When looking at expired vApps in the vCloud UI, the owner’s information isn’t displayed.   This makes it difficult to determine who to contact about the expired vApp.  In the past I ran the following PowerCLI script to display the expired vApps, owner login, owner name, the VMs in the vApp and their organization.

get-civapp | ? { try { ($expirationDate = $_.ExtensionData.Section[0].StorageLeaseExpiration) -ne $null -and (get-date) -gt $expirationDate } catch {} } | `
    select name,`
           @{N = 'Owner'; E = { if ($_.owner.fullname) { $_.Owner.Name + ' - ' + $_.Owner.FullName } else { $_.Owner.Name }}}, `
           @{N = 'Expiration Date'; E = { $expirationDate }}, `
           @{N = 'Org'; E = { $_.Org }}, `
           @{N = 'Description'; E = { $_.Description }}, `
           @{N = 'Virtual Machines' ; E = { (($_ | get-civm | select name) | % { $vms + $ }) -join ", " }} | sort 'Expiration Date', org

I dug through the PowerCLI objects and couldn’t find way of determing if a vApp was in an expired state.  If it’s there, please let me know.  However, I did see that if the vApp was expired, vApp.ExtensionData.Section[0].StorageLeaseExpiration would be set to a date (otherwise it would be empty), and if this date was in the past, then the vApp would be in the expired items (assuming you had vCloud configured to not delete them).   The script above worked, but it was slow in our environment (8 minutes to run).  It was put in a nightly report so it wasn’t that big of a deal, but it always bothered me.

I knew about the Search-Cloud PowerCLI commandlet but hadn’t had time to look into it.  I finally decided to play around with it and found that it could query vCloud objects much faster than using a PowerCLI’s ExtensionData method, which I think just gets the objects view and that is basically everything about the object.   I replaced the above script with the following.

# Create lookup tables for orgs and users
$orgs = @{}
$users = @{}

# Populate lookup tables 
# The mappings will ljook like
# Name                           Value
# ----                           -----
# urn:vcloud:org:243d3468-93e... admin
# urn:vcloud:org:cd07ebc8-620... sales
# urn:vcloud:org:b99aa87f-eab... engineering

Search-Cloud -QueryType Organization | % { $orgs[$] = $ }
Search-Cloud -QueryType Adminuser | % { $users[$] = $_.fullname }

# Search for expired vApps and select the name, owner's name, owner's full name and org.  We deteremine the org's name by taking the urn that is returned by Search-Cloud and looking it up in the org lookup table to find its friendly name. 
Search-Cloud -QueryType AdminVApp -filter 'IsExpired==True' | select name, 

This script completes in about one or two seconds.  Since Search-Cloud doesn’t return everything that ExtentionData does, which is one reason it’s faster, you don’t get back info such as the organization name.  Instead of the org name, you get back the org’s urn (for example, urn:vcloud:org:a56f6a46-ff33-4174-869f-129676d70a4f).

This is easy to work around by building a lookup table that maps a urn to an org/user name.  You just have to call Search-Cloud again and change the query type to organization/adminuser.  The returned info will have the org name and urn.  Once you have the the org/user urn, you can look up the friendly name in the orgs/users lookup tables.  For example, in the select statement, you see the funny looking entry “@{N=’Org’;E={$orgs[$]}}”.  Looking at the E (Expression) section, you see that it equals $orgs[$].  This is taking the current object’s ($_) org urn and looking it up in the orgs lookup table.  For the first object, $ will be ‘urn:vcloud:org:243d3468-93e’.  We then take this value and place it into the orgs lookup table.

$orgs[‘urn:vcloud:org:243d3468-93e’] = admin

The result is ‘admin’, which will be displayed as the org name in the Search-Cloud query.


PowerCLI script to change a distributed switch’s default settings – Updated for vSphere 5.5

I previously posted a script on how to change a distributed switch’s default settings.  I’ve updated it to work on vSphere 5.5 and made some other improvements.

$vDSName = '' # Enter the name of the vDS here
$loadBalancingPolicy = 'loadbalance_ip' # IP hash
$numActiveUplinkPorts = 2
$activeUplinkNames = @('Uplink 1', 'Uplink 2')

$vds = Get-VDSwitch $vDSName
$spec = New-Object VMware.Vim.DVSConfigSpec
$spec.configVersion = $vds.ExtensionData.Config.ConfigVersion

$spec.defaultPortConfig = New-Object VMware.Vim.VMwareDVSPortSetting
$uplinkTeamingPolicy = New-Object VMware.Vim.VmwareUplinkPortTeamingPolicy

# Set load balancing policy to IP hash
$uplinkTeamingPolicy.policy = New-Object VMware.Vim.StringPolicy
$uplinkTeamingPolicy.policy.inherited = $false
$uplinkTeamingPolicy.policy.value = $loadBalancingPolicy

# Configure uplinks. If an uplink is not specified, it is placed into the ‘Unused Uplinks’ section.
$uplinkTeamingPolicy.uplinkPortOrder = New-Object VMware.Vim.VMwareUplinkPortOrderPolicy
$uplinkTeamingPolicy.uplinkPortOrder.inherited = $false
$uplinkTeamingPolicy.uplinkPortOrder.activeUplinkPort = New-Object System.String[] ($numActiveUplinkPorts)

for ($i = 0; $i -lt $activeUplinkNames.length; $i++) {
$uplinkTeamingPolicy.uplinkPortOrder.activeUplinkPort[$i] = $activeUplinkNames[$i]

# Set notify switches to true
$uplinkTeamingPolicy.notifySwitches = New-Object VMware.Vim.BoolPolicy
$uplinkTeamingPolicy.notifySwitches.inherited = $false
$uplinkTeamingPolicy.notifySwitches.value = $true

# Set to failback to true
$uplinkTeamingPolicy.rollingOrder = New-Object VMware.Vim.BoolPolicy
$uplinkTeamingPolicy.rollingOrder.inherited = $false
$uplinkTeamingPolicy.rollingOrder.value = $true

# Set network failover detection to “link status only”
$uplinkTeamingPolicy.failureCriteria = New-Object VMware.Vim.DVSFailureCriteria
$uplinkTeamingPolicy.failureCriteria.inherited = $false
$uplinkTeamingPolicy.failureCriteria.checkBeacon = New-Object VMware.Vim.BoolPolicy
$uplinkTeamingPolicy.failureCriteria.checkBeacon.inherited = $false
$uplinkTeamingPolicy.failureCriteria.checkBeacon.value = $false

$spec.DefaultPortConfig.UplinkTeamingPolicy = $uplinkTeamingPolicy