<Update – August 25th, 2014>
Hello! If you are reading this you might be also interested in an updated version of this script, that I posted here.
It includes background jobs, asynchronous tasks and displays colorful messages too!
</Update>
First of all – I’d like to introduce myself as this is the first blog post I ever wrote in my life
My name is Sebastian Baryło and Artur was brave enough to invite me to contribute to his blog every now and then.
I am that kind of lazy person who prefers to spend two days on writing and testing a script rather than spend half a day on manual iterations of tedious tasks, so most of my posts here would be in “PowerCLI scripts” category, but I will try to share my knowledge on anything interesting that I experienced during my daily struggle of being system engineer.
What I would like to share today is “yet another vm deployment script” that might come handy when you want to automate deployment of a batch of virtual machines (in my case they were windows desktops)
Of course having a proper template and OS Customization Specification are pre-requisites, once you have them you need to prepare (or better yet – request from your customer) input for the script in the form of .csv file (let’s call it vms2deploy.csv) that should look like this.
name,template,oscust,cluster,folder,datastore-cluster,ip,mask,gw,dns1,dns2 winxp_01,winxp_template,winxp_custom-spec,VDI-Cluster,WinXP-Desktops,VDI-StorageCluster,10.0.0.75,255.255.255.0,10.0.0.1,10.0.0.32,10.0.0.34 winxp_02,winxp_template,winxp_custom-spec,VDI-Cluster,WinXP-Desktops,VDI-StorageCluster,10.0.0.76,255.255.255.0,10.0.0.1,10.0.0.32,10.0.0.34 win2k3_01,win2k3_template,win2k3_custom-spec,Server-Cluster,Win-Servers,Servers-StorageCluster,10.0.1.77,255.255.255.0,10.0.1.1,10.0.0.32,10.0.0.34
I hope it is pretty self-explanatory what does each of the fields mean, please note that I’m using datastore clusters here, but it is very easy to replace them with single datastores (get-datastorecluster cmdlet in the script just needs to be replaced with get-datastore)
Now that we know what to deploy, it is time to introduce the script.
<# .SYNOPSIS Script automates deployment of multiple vms loaded from pre-defined .csv file .DESCRIPTION Script reads the .csv file, deploys vms from template, applies os customization spec then attempts to set up IP configuration .PARAMETER none .EXAMPLE trivia #> #variables $ScriptRoot = Split-Path $MyInvocation.MyCommand.Path $csvfile = "$ScriptRoot\vms2deploy.csv" $vcenter_srv = 'vcenter.seba.local' $timeout = 1800 $loop_control = 0 $vmsnapin = Get-PSSnapin VMware.VimAutomation.Core -ErrorAction SilentlyContinue $Error.Clear() if ($vmsnapin -eq $null) { Add-PSSnapin VMware.VimAutomation.Core if ($error.Count -eq 0) { write-host "PowerCLI VimAutomation.Core Snap-in was successfully enabled." -ForegroundColor Green } else { write-host "ERROR: Could not enable PowerCLI VimAutomation.Core Snap-in, exiting script" -ForegroundColor Red Exit } } else { Write-Host "PowerCLI VimAutomation.Core Snap-in is already enabled" -ForegroundColor Green } # 32-bit PowerCLI is required to run *-OSCustomizationSpec cmdlets if ($env:Processor_Architecture -eq "x86") { #Connect to vCenter Connect-VIServer -Server $vcenter_srv $vms2deploy = Import-Csv -Path $csvfile #deploy vms as per information in each line, wait for customization (a reboot) then wait for vmware tools to change ip settings foreach ($vm in $vms2deploy) { #validate input, at least vm name, template name and OS Customization Spec name should be provided if (($vm.name -ne "") -and ($vm.template -ne "") -and ($vm.oscust -ne "")){ #check if vm with this name already exists (some funny results are produced once we deploy vm with duplicate name) if (!(get-vm $vm.name -erroraction 0)){ $vmhost = get-cluster $vm.cluster | get-vmhost -state connected | Get-Random #check if we want to attempt IP configuration, if "none" is written in .csv file (because we use DHCP for example) we deploy immediately, otherwise we insert IP into CustomizationSpec, then deploy if ($vm.ip -match ‘none’){ Write-Host "No IP configuration in .csv file, moving on" -ForegroundColor Yellow write-Host "Deploying VM $($vm.name) to datastore cluster $($vm.datastore-cluster)" new-vm -name $vm.name -template $(get-template -name $vm.template) -vmhost $vmhost -oscustomizationspec $(get-oscustomizationspec -name $vm.oscust) -datastore $(get-datastorecluster -name $vm.datastore-cluster) -location $(get-folder -name $vm.folder) | Out-Null } else { #clone the "master" OS Customization Spec, then use it to apply vm specific IP configuration $cloned_oscust = Get-OSCustomizationSpec $vm.oscust | New-OSCustomizationSpec -name "$($vm.oscust)_$($vm.name)" Set-OSCustomizationNicMapping -OSCustomizationNicMapping ($cloned_oscust | Get-OscustomizationNicMapping) -Position 1 -IpMode UseStaticIp -IpAddress $vm.ip -SubnetMask $vm.mask -DefaultGateway $vm.gw -Dns $vm.dns1,$vm.dns2 | Out-Null write-Host "Deploying VM $($vm.name) to datastore cluster $($vm.datastore)" new-vm -name $vm.name -template $(get-template -name $vm.template) -vmhost $vmhost -oscustomizationspec $cloned_oscust -datastore $(get-datastorecluster -name $vm.datastore-cluster) -location $(get-folder -name $vm.folder) | Out-Null } #this is where we try to track deployment progress $loop_control = 0 write-host "Starting VM $($vm.name)" start-vm -vm $vm.name -confirm:$false | Out-Null write-host "Waiting for first boot of $($vm.name)" -ForegroundColor Yellow do { $toolsStatus = (Get-VM -name $vm.name).extensiondata.Guest.ToolsStatus Start-Sleep 3 $loop_control++ } until ( ($toolsStatus -match ‘toolsOk’) -or ($loop_control -gt $timeout) ) write-host "Waiting for customization spec to apply for $($vm.name) (a reboot)" -ForegroundColor Green do { $toolsStatus = (Get-VM -name $vm.name).extensiondata.Guest.ToolsStatus Start-Sleep 3 $loop_control++ } until ( ($toolsStatus -match ‘toolsNotRunning’) -or ($loop_control -gt $timeout) ) Write-Host "OS customization in progress for $($vm.name)" -ForegroundColor red do { $toolsStatus = (Get-VM -name $vm.name).extensiondata.Guest.ToolsStatus Start-Sleep 3 $loop_control++ } until ( ($toolsStatus -match ‘toolsOk’) -or ($loop_control -gt $timeout) ) #wait another minute "just in case" feel free to remove this line Start-Sleep 60 #clean-up the cloned OS Customization spec Remove-OSCustomizationSpec -CustomizationSpec $cloned_oscust -Confirm:$false | Out-Null if ($loop_control -gt $timeout){ Write-Host "Deployment of $($vm.name) took more than $($timeout/20) minutes, check if everything OK" -ForegroundColor red } else { Write-Host "$($vm.name) successfully deployed, moving on" -ForegroundColor Green } } else { Write-Host "$($vm.name) already exists, moving on" -ForegroundColor Red } } else { Write-Host "Check input file $csvfile for line with empty vm, template or OS customization spec value" -ForegroundColor Red } } Write-Host "All vms deployed, exiting" -ForegroundColor Green #disconnect vCenter Disconnect-VIServer -Confirm:$false } else { Write-Host "This script should be run from 32-bit version of PowerCLI only, Open 32-bit PowerCLI window and start again" -ForegroundColor Red }
It is pretty lenghty – I know, but I blame it on the comments 🙂
Not really rocket science with deployment part – it is just feeding new-vm cmdlet with what we read from .csv, you can easily extend functionality here with customizing things like number of cpus or amount of memory per vm, the more interesting part for me is how we configure IP settings during deployment.
At first I experimented with set-vmguestnetworkinterface and invoke-vmscript cmdlets, but I wasn’t very successful as above cmdlets have very specific requirements that you can find described in excellent post of LucD here.
Finally I decided to use OS Customization Specification to configure IP settings for deployed vm, the script first creates a “temporary clone” of “master” OS customization provided in .csv file, modifies it to include IP settings specified, deploys and powers-on the vm and cleans up by deleting “temporary clone” of OS customization.
Please note that to run “oscustomizationspec related” cmdlets you need to use 32-bit version of PowerCLi.
You may wonder about the sequence of three “until-loops” that I used to control whether deployment is successful, I know it is not very elegant (proper way would probably be to query vCenter for events like “CustomizationStarted” etc) but it just does the job.
First loop waits until vmware tools are running for freshly deployed vm (so the intial power-on is successful),
the second waits for a reboot initiated by OS Customization (at least in case of deploying Windows XP) and the final is running in circles until vmware tools are available again (customization successful).
You can probably remove third loop (and proceed with deployment not waiting for completion of OS customization – which is usually the longest part).
As you can see there is also some timeout control introduced, vmware tools are checked every 3 seconds, so $timeout value of 1800 gives us not more than 90 minutes of waiting for deployment of single vm (it is very long – I know, but the way I was using this script is to fire it at the end of business day and check the results next morning).
What can be improved?
You might want to run many “new-vm” tasks simultaneously (with start-job cmdlet for example), this should be possible since each vm deployed is using its own copy of OS customization.
And yeah – sanitizing input (checking for empty fields in .csv file) could have probably be done in one line, but I’m a bit “old-school” when it comes to scripting and I like to be able to see what is going on there 😉
I hope you find this script useful, any comments are welcome!
Hi there – great script…even without powershell-jobs and with until-loops 😉
How did you manage to get the OS Customizations working for Windows Server 2012 (and 2012R2)?
hi i tried this script it works for me but i am getting error for this oscustomizationspec can you please tell what is this and how to create . and the ipaddress are not assigning to the vm .
[…] is to automate a DC deployment with a fixed IP, DHCP, some policies and some DNS modifications. Sebastian Baryło posted a script to batch multiple clone vm’s with a specific ip configurations, i like […]
If you copy the script from the website, make sure you check for non printable characters. I found a bunch around the match commands.