Simple PowerCLi script for batch virtual machine deployment

Shares

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

0 0 votes
Article Rating

Sebastian Baryło

Successfully jumping to conclusions since 2001.

You may also like...

Subscribe
Notify of
guest
8 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Chris

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)?

venkatesh

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 […]

Daniel Hill

If you copy the script from the website, make sure you check for non printable characters. I found a bunch around the match commands.

8
0
Would love your thoughts, please comment.x
()
x

FOR FREE. Download Nutanix port diagrams

Join our mailing list to receive an email with instructions on how to download 19 port diagrams in MS Visio format.

NOTE: if you do not get an email within 1h, check your SPAM filters

You have Successfully Subscribed!

Pin It on Pinterest