Provisioning Clones using Modified Customization Specifications and Specific Port Groups

I received an email asking about the relationship between VMware’s Customization Specifications and the resulting port group that a VM will be placed upon. In essence, once a VM has been deployed from a template using a Customization Specification, how does vCenter know which network port group should be used? The short answer is that vCenter doesn’t know which port group to use, and that the port group being used by the source template VM will also be used by the cloned target VM.

Stepping back a bit, Customization Specifications are a method for customizing a VM’s guest operating system. You are still on the hook for making sure that VM specific configuration items are set properly, such as picking the correct port group, storage, compute sizing, and so on. The Customization Specification is not involved with that process, but does handle these items:

  • Guest computer name
  • Windows OS licensing
  • Windows administrative password
  • Time zone
  • Network information (IP, mask, gateway, DNS, WINS)
  • Windows Domain or workgroup settings
  • Windows SID (Security ID) generation
  • Windows in-guest scripting using Run Once

Subsequently, if you were to use a VM as a template that resided on port group SpongeBob, the resulting VM that is being deployed from the template will also be on port group SpongeBob. Changing the port group would be outside the scope of the Customization Specification, and from my experience using PowerCLI, the cloning process won’t allow you to change the port group during the clone operation with a Portgroup argument. But that doesn’t mean that you can’t just serialize that process and put it on the tail end of a script.

Building a Workflow

One strategy that I have used in the past is leveraging PowerCLI to act as an orchestration engine for simple provisioning. I wrote about this years back in the Dynamic VM Provisioning using PowerCLI and vSphere Customization Specifications post and will expand upon the idea further.

From a high level, this is a sample of the workflow.

  1. Select either a VM or a VM Template to act as the source for the clone operation. I prefer to use powered off VMs as my templates.
  2. Gather network details from the user or an external resource (IPAM server, CSV file, DHCP relay for a segment, etc.).
    • This would either automatically parse out which port group should be used for the target VM, or allow the user to select from a list.
    • In most cases, the subnet itself can give you details as to which port group to use.
  3. Update a special Customization Specification with the network details.
    • This is a Customization Specification used specifically for PowerCLI and not for manual provisioning because we’re going to alter its configuration with each run.
    • Relevent cmdlets would be Set-OSCustomizationSpec and Set-OSCustomizationNicMapping.
    • I prefer to use an existing master specification that contains secured domain credentials and license keys rather than hard coding those details into a script file.
  4. Start a New-VM (clone operation) task using the modified Customization Specification along with the potential to use a different datastore and resize the VM.
  5. Once the clone completes, the target VM is placed within the desired network port group via script (preferred) or by hand. It can now be powered on and customized.

A few thoughts on this:

  • I avoid changing the source VM’s network because there may be other clone tasks in progress which locks changes to the source.
  • Although I don’t convert my VMs into Templates (personal opinion: VM Template format sucks for patching and managing), those who do are likely familiar with the fact that you can’t edit their network until they are converted into a VM.
  • It’s more responsible to edit the cloned VM’s configuration because you ensure it looks the way you want it to, which I equate to the warm and fuzzies.

Sample Script

I’ve provided an example script to demonstrate the process via PowerCLI. It is meant to be a starting point to understand the workflow, not an end state script for production. You could also experiment with using Linked Clones if you want to just test the scripting process without waiting for an entire clone to finish.

$vc = "vc1.glacier.local"
$newvm = "Example VM"
$template = "Server 2012 R2 Template"
$custspec = "2012R2_DataCenter_PowerCLI"
$portgroup = "VLAN20-Servers"
Add the required PowerCLI snapin/module
Import-Module VMware.VimAutomation.Vds -ErrorAction Stop
Write-Host -ForegroundColor Yellow -BackgroundColor Black "Status: PowerCLI version 6.0+ found."
try {Add-PSSnapin VMware.VimAutomation.Vds -ErrorAction Stop} catch {throw "You are missing the VMware.VimAutomation.Vds snapin"}
Write-Host -ForegroundColor Yellow -BackgroundColor Black "Status: PowerCLI prior to version 6.0 found."
Connect to vCenter
Connect-VIServer $vc | Out-Null
Update the Customization Specification
Get-OSCustomizationSpec $custspec | Get-OSCustomizationNicMapping
| Set-OSCustomizationNicMapping -IpMode:UseStaticIP
-IpAddress (Read-Host "IP Address: ") -SubnetMask (Read-Host "Subnet Mask: ")
-Dns (Read-Host "DNS Server IP: ") `
-DefaultGateway (Read-Host "Default Gateway IP: ")
Clone the VM
New-VM -VM $template -Name $newvm -LinkedClone -ReferenceSnapshot "LinkedClone" -Location (Get-Folder Staging) -ResourcePool (Get-Cluster Lab) -OSCustomizationSpec $custspec
Move VM to correct port group
try {Get-VM $newvm | Get-NetworkAdapter | Set-NetworkAdapter -NetworkName $portgroup -Confirm:$false -ErrorAction Stop}
catch {break}

There’s also much more robust ways of doing this with orchestration tools such as vRealize Orchestrator, but it never hurts to understand how to do these things with a brief script, either. Note that the example script doesn’t account for more challenging scenarios such as VMs with multiple network adapters, but I’m sure a little work with a hashtable can solve that.