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 derekseaman.com
#

# Your vcenter server name
$vCenter = "vcenter.domain.com"

# 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
$ReplaceHost = "CHANGEHOSTNAME"

# 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);
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($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, $VM.name) } | Set-Content $CustomSysprep
write-host $vm.name 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 $VM.name -guestuser $guestuser -GuestPassword $GuestPassword | out-null
write-host $vm.name 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 $VM.name -localtoguest -guestuser $guestuser -guestpassword $guestpassword
invoke-vmscript -scripttext $script2 -VM $VM.name -guestuser $guestuser -GuestPassword $GuestPassword -scripttype bat -runasync | out-null
write-host $vm.name Sysprep executed
}

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

  1. Thanks Derek! I've been looking for a solution to automate the sysprep process when using linked clones. I've been manually adjusting the unattend.xml, setupcomplete.cmd, and using MySysprep2 to help. I think your scripts give me a better way to automate these things. Thanks so much for sharing!

  2. Derek,

    Nice script! I've built up a web app to clone vm's as part of our provisioning process, and one of the guys I work with pointed me to this. I would be interested in seeing your unattend.xml file, the ones that get created by VMware seem overly complicated.

    Also, if you're interested here is the app I created.
    https://code.google.com/p/vmware-net/

  3. This script was a total win. I found another script that worked well enough to generate linked clones via powerCLI but faced the issue of automating SYSPREP/customization (incl NAME, SID, etc). After creating a master XML answer file including a join to the domain piece, this script, after some very minor tweaks and troubleshooting worked in our environment lab. HAnds free customization and joining to the domain after making the linked clones. This will be a huge help in automating rapid deployment of linked clone VMs in our production! Thank you!!!

  4. One other thing to add, after playing around, I changed the for loop a tiny bit to suit my needs, I don't need the CSV file to import a list of machine names, since in my unique case, the VM clones all have the same base name with a number appended to the end. So I used traditional loop with numbers 01..05 | foreach { } and inside the loop I have a variable that is assigned the partial vm name plus appended the value of the current loop iteration (01, or 02 or 05) to it by wrapping the string variable like this

    $partialVMname ="basevmname"
    01..05 | foreach {
    $VMname = "$($partialVMname)$_" #appends loop value of $_ to base vm name for full vm name

    then rest of code here, replacing $vm.name (from the original code above) with $VMname…..This allows me to not need to edit a CSV file and only type the partial or base VM name in 1 place, where I might be spinning up 50 at a time or whatever. Again works in my circumstance since the VMnames are consecutive with a number at the end. Hope this helps someone else.

  5. Just wanted to say a big "thank you" for this script. It came to my rescue and saved me a load of time. Very grateful that you shared it – get yourself a beer! ;o)

  6. Sorry to be a pain but I am need some help, when I run the script it fails at trying to copy the file. The local admin can login without issue. Any help would be appreciated.

    Thanks
    Greg

    Copy-VMGuestFile : 1/23/2014 1:07:16 PM Copy-VMGuestFile Unable to ac
    cess file c:windowssystem32sysprepsysprep.xml
    At E:tempSysprep.ps1:70 char:17
    + copy-vmguestfile <<<< -source $CustomSysprep -destination c:windowssystem3
    2sysprep -VM $VM.name -localtoguest -guestuser $guestuser -guestpassword $gues
    tpassword
    + CategoryInfo : NotSpecified: (:) [Copy-VMGuestFile], CannotAcce
    ssFile
    + FullyQualifiedErrorId : Client20_VmGuestServiceImpl_UploadFileToGuest_Vi
    Error,VMware.VimAutomation.ViCore.Cmdlets.Commands.CopyVMGuestFile

      • Sorry I posted the same message, something is off doing this from my iphone.

        When you say XML file already exist do you mean d:sysprep.xml?

        Is it possible to get a copy of your master XML file?

        Greg Cottone at hotmail dot com

        Thanks
        Greg

  7. Copy-VMGuestFile : 1/23/2014 3:18:38 PM Copy-VMGuestFile Unable to ac
    cess file c:windowssystem32sysprepsysprep.xml
    At E:TempSysprep.ps1:70 char:17
    + copy-vmguestfile <<<< -source $CustomSysprep -destination c:windowssystem3
    2sysprep -VM $VM.name -localtoguest -guestuser $guestuser -guestpassword $gues
    tpassword
    + CategoryInfo : NotSpecified: (:) [Copy-VMGuestFile], CannotAcce
    ssFile
    + FullyQualifiedErrorId : Client20_VmGuestServiceImpl_UploadFileToGuest_Vi
    Error,VMware.VimAutomation.ViCore.Cmdlets.Commands.CopyVMGuestFile