Automate Sysprep on vSphere w/o Custom Specs

I’m a huge fan of using vCenter customization specifications to automate the sysprep process for deploying new Microsoft VMs. The sysprep process ensures a unique Windows SID, sets the VM’s hostname, and can even join a VM to the domain, among other things. However, the customization specifications can only be triggered when you clone a VM. While that may be good for a vast majority of use cases, I recently ran across a scenario where that was not possible since my VMs already existed.

For a VDI project I am looking at a software storage appliance to offload a lot of the IOPS from our back end storage system, to increase performance and reduce costs. One feature the our particular solution has is called “fast clone”, which allows the storage appliance to create a VM clone in just a few seconds, instead of several minutes using the vCenter clone method. Internally it adjusts some pointers to the VMDK, and de-dupes, so it doesn’t have to copy every block when you create a new VM. In fact, very few blocks are copied during the cloning process.

However the “fast clone” process literally cloned the master VM and did not have any method to trigger vCenter customization specs. As a result all the Windows hostnames were the same as were the SIDs. I certainly did not want to run sysprep manually on hundreds of VMs. The vendor workaround was far too complex and cumbersome to consider. So I developed the script below which automates the major tasks which the vCenter customization specifications perform and easier (IMHO) than what the vendor suggested.

Script Features

  • Copies an existing sysprep unattend XML file to the VM via the VMware tools VIX interface
  • Each unattended XML file is automatically customized with the VM’s name as it appears in vCenter, so sysprep will change the Windows hostname appropriately
  • Deletes the residual unattended XML files which may contain sensitive passwords or product keys
  • Auto-joins the VM to the domain assuming an appropriately configured unattend XML file and DHCP is available
  • Accepts a command line argument for easy testing against one VM, but it will also read a CSV file for mass processing

It’s up to you to supply an appropriately configured Windows sysprep unattended XML file for the operating system in question. If you include domain join parameters then it will join the VM to the domain as well, all without prompting for a username or password. To delete the residual XML files, the script will upload a setupcomplete.cmd file to c:\windows\setup\scripts. It will not over-write any existing file, so make sure it doesn’t exist. Windows knows to automatically run that script after the sysprep process.

In order to customize the unattended XML file with the VM’s hostname, the script does a simple replace on a string called “CHANGEHOSTNAME”. When you create your XML file be sure to use this name for the machine name, so the search and replace will work properly. Otherwise all the VMs will have the same hostname!

5-18-2013 6-35-18 PM

Using the Script

When you want to run the script against several machines, use the csv option. The csv file must have the vCenter VM name, one VM per line, without any header or empty lines at the end. There’s limited error checking, so I would urge you to take a snapshot of your target VM so you can revert back until you work out the kinks with your unattend file. In the vCenter console you will see some authentication errors when sysprep kicks off and invoke-script can no longer connect to the VM , but those are harmless messages.

In the example below I executed the script on the vCenter server using a PowerCLI console. I had configured the CSV input file with two hostnames. First I entered my password (for my current user account), then the administrator credentials for the guest VM. The script assumes all VMs have the same credentials as you will only be prompted once.

vsphere sysprep windows

If you watch the vCenter console you will see a bunch of entries. As I mentioned earlier, once sysprep kicks off vCenter is unable to connect to the guest so some authentication errors appear.

vsphere sysprep windows

After minute or so the VM rebooted and the sysprep process kicked off. A few minutes later my VM was joined to the domain with its new name and ready for use. Depending on the complexity of your unattended sysprep file you do could a lot of customization within the guest, install software, etc. the sky is really the limit. This script just gives you an easy way to run sysprep against dozens or hundreds of existing VMs if you can’t use vCenter customization specifications.

5-18-2013 6-52-35 PM

# This script will copy a sysprep unattend XML file to the guest VM and execute it,
# using the VM's vCenter name. Input can be a single arguement on the command line,
# or a csv file. The CSV must have one VM name per line and no blank lines or header.
# The setupcomplete.cmd deletes the two copies of the unattend XML file, which may
# contain sensitive passwords or product keys.
# Derek Seaman

# Your vcenter server name
$vCenter = ""

# Your master sysprep unattended file. It will not be modified.
$MasterSysprep = "d:\sysprep-master.xml"

# Optional CSV input file. Only called if no VM argument is provided.
# One vCenter VM name per line with no header
$CSV_File = "D:\vms.csv"

# "Hostname" in the master unattended sysprep file that will be replaced for each VM

# Resulting sysprep file with the custom hostname, overwritten for each VM. Do not change.
$CustomSysprep = "D:\sysprep.xml"

# Don't change anything below here

#Validates VMware PowerCLI snap-ins are loaded

$xPsCheck = Get-PSSnapin | Select Name | Where {$_.Name -Like "*VMware*"}
If ($xPsCheck -eq $Null) {Add-PsSnapin VMware.VimAutomation.Core}
if ($args[0] -eq $null ) {$list = import-csv $CSV_File -header name} else { $list = $args[0] }

# Function to mask password input
function Read-HostMasked([string]$prompt="Password") {
$password = Read-Host -AsSecureString $prompt;
$BSTR = [System.Runtime.InteropServices.marshal]::SecureStringToBSTR($password);
$password = [System.Runtime.InteropServices.marshal]::PtrToStringAuto($BSTR);
return $password;

} # function Read-HostMasked([string]$prompt="Password")

# Connects to vCenter
$currentUser = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).Name
$currentUsePassword = Read-HostMasked "Enter the password for the current user"
Connect-VIServer -Server $vCenter -User $currentUser -Password $currentUsePassword | out-null

# Guest OS administrator credential input
$guestuser = read-host "Enter guest administrator username"
$guestpassword = read-HostMasked "Enter guest administrator password"

Foreach ($vm in $list) {

# Cleans up prior local sysprep output file and replaces hostname in sysprep.xml
remove-item $CustomSysprep -ErrorAction SilentlyContinue

$content = Get-Content $MasterSysprep
$content | foreach { $_.Replace($ReplaceHost, $ } | Set-Content $CustomSysprep
write-host $ Custom sysprep file created

# Creates setupcomplete.cmd file to delete sysprep XML files post-sysprep. File must not already exist.
$Script1 = "echo `"del /F /Q c:\windows\panther\unattend.xml c:\windows\system32\sysprep\sysprep.xml`" | out-file -encoding ASCII c:\windows\setup\scripts\setupcomplete.cmd"
invoke-vmscript -scripttext $script1 -VM $ -guestuser $guestuser -GuestPassword $GuestPassword | out-null
write-host $ setupcomplete.cmd uploaded

# Copies sysprep.xml to guest and executes asynchronously
$script2 = "c:\windows\system32\sysprep\sysprep.exe /generalize /oobe /unattend:c:\windows\system32\sysprep\sysprep.xml /reboot"
copy-vmguestfile -source $CustomSysprep -destination c:\windows\system32\sysprep -VM $ -localtoguest -guestuser $guestuser -guestpassword $guestpassword
invoke-vmscript -scripttext $script2 -VM $ -guestuser $guestuser -GuestPassword $GuestPassword -scripttype bat -runasync | out-null
write-host $ Sysprep executed

Speak Your Mind


© 2017 - Sitemap