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
Print Friendly, PDF & Email

Related Posts

Notify of
Newest Most Voted
Inline Feedbacks
View all comments
May 19, 2013 12:14 am

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!

May 30, 2013 6:26 am


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.

August 7, 2013 12:48 pm

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!!!

August 8, 2013 11:52 am

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… Read more »

August 9, 2013 5:43 am

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)

Need Help
January 23, 2014 11:43 am

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.


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 $ -localtoguest -guestuser $guestuser -guestpassword $gues
+ CategoryInfo : NotSpecified: (:) [Copy-VMGuestFile], CannotAcce
+ FullyQualifiedErrorId : Client20_VmGuestServiceImpl_UploadFileToGuest_Vi

March 28, 2014 4:24 pm
Reply to  Derek Seaman

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


April 11, 2014 12:14 pm
Reply to  Derek Seaman

The XML file does not exist on the client VM. Yes, latest version of VMware tools is being used.

Need Help
January 23, 2014 1:24 pm

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 $ -localtoguest -guestuser $guestuser -guestpassword $gues
+ CategoryInfo : NotSpecified: (:) [Copy-VMGuestFile], CannotAcce
+ FullyQualifiedErrorId : Client20_VmGuestServiceImpl_UploadFileToGuest_Vi

June 24, 2020 1:35 am

Thanks Derek, It,s very usefull article.

Last edited 3 years ago by Ozgur