Deploy VMware VM’s with PowerCLI
Welcome to my first post! Today I am going to show you how to easily deploy VM's using VMware's PowerCLI, and PowerShell. This is all possible through the vCenter client, but I believe most of the questions asked can be skipped in favor of static parameters. For this reason, we will only see 3 prompts when running this script. This could be cut down even further if you allow DHCP to set the IP.
I would like to point out two things about myself at this time. For one, format and style matters to me. You will notice that I like my scripts to look good (they look good to me!). This adds additional lines and unnecessary code, but it helps me sleep at night! The second bit is that I like to use functions. The reason for this is that I like to have the ability to recall the function if there is an issue.
foolhardily What it does
This script will allow you to easily deploy a VM, set network information, join it to the domain, and move it to the proper OU. Essentially, you can go from nothing to a production ready VM in just a few minutes.
Basavakalyān What you need and need to know
- PowerCLI must be installed
- You must use the x86 executable (32bit version) for this script to work properly!
- A Server 2008 / 2008 R2 template (Warning: Do not run sysprep on this template!!)
Learn it
I've tried to make these scripts as usable as possible, thus I like to reuse functions like ConnectAD. All this function does is open a New-PSSession. This allows us to use AD commands when we don't have any AD tools installed. I also avoid the use of Quest AD tools.
1 2 3 4 5 |
function ConnectAD { $session = New-PSSession -ComputerName "$dc" Invoke-Command -Session $session -ScriptBlock {Import-Module ActiveDirectory} Import-PSSession -Session $session -Module ActiveDirectory } |
Load the modules (PowerCLI and AD), unless they have already been loaded.
1 2 3 4 5 6 7 8 9 |
#Load modules if(!(Get-PSSession | ?{$_.ComputerName -like "$dc*"}) -and $askoncepersessionmodules -ne 1) { $global:askoncepersessionmodules = 1 cls write-host "Connecting to Active Directory, and VIServer. Please wait..." -f yellow; add-pssnapin VMware.VimAutomation.Core ConnectAD connect-viserver $VIServer } |
Gather server information in a presentable manner. Please note that you must enter the full Datastore name. It would be possible to use the datastore ID, but I wanted this to be typed out so as to avoid any mistakes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#Setup Server info function SetupServer($error) { cls write-host "`n---------------------------------" -f yellow write-host "$window_title" -f green write-host "---------------------------------`n" -f yellow # If $error is not $null, display what the error is. if($error) { write-host "$error`n" -f red } # Gather server information $ServerName = (Read-Host "Server Name").ToUpper() $IPAddress = Read-Host "IP Address" $DefaultGateway = Read-Host "Default Gateway" #Create datastore list $datastores = get-datastore | Sort Name foreach($datastore in $datastores) { if($datastore -notlike "datastore*") { $spacefree = round ($datastore.FreeSpaceMB / 1024) $characters = $datastore | measure-object -character | select -expandproperty characters $maxcharacters = 18; $needed = $maxcharacters - $characters; $count = 1; $periods = $null; while($count -le $needed) { $periods = "$periods."; $count++; } if($spacefree -lt 100) { write-host "$datastore$periods " -nonewline -f red; write-host "$spacefree GB" -f red; } elseif($spacefree -lt 500) { write-host "$datastore$periods " -nonewline -f yellow; write-host "$spacefree GB" -f yellow; } else { write-host "$datastore$periods " -nonewline -f green; write-host "$spacefree GB" -f green; } } } # Which datastore do we want to select? $DatastoreName = (Read-Host "Datastore Name").ToUpper() if(!(get-datastore $DatastoreName -ErrorAction SilentlyContinue)) { $error = "Datastore $DatastoreName not found." SetupServer($error) } #Check our work if(!$ServerName -or !$IPAddress -or !$DefaultGateway -or !$DataStoreName) { $error = "Server Name, IP Address, Default Gateway, and Datastore Name cannot be left blank." SetupServer($error) } else { $result = $null $message = "`nAre you sure you want to create this server? ($ServerName; $IPAddress; $DefaultGateway; $DataStoreName)" $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes","" $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "" $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) $result = $host.ui.PromptForChoice($title, $message, $options, 0) if($result -eq 0) { Create-VM } else { SetupServer } } } |
And lastly, we need to actually create the VM. In a nutshell, all we're doing here is taking all the variables that we've put together up to this point, and throwing it all together in a Simple Customizations Spec, which for our purpose is temporary. Once we've created the VM, we power it on, move it to the proper OU, and that's it!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
function Create-VM { cls write-host "************ Creating $ServerName; DO NOT CLOSE/STOP THIS SCRIPT! ************" -f red #Create a simple customizations spec: $custSpec = New-OSCustomizationSpec -Type NonPersistent -ChangeSID -OSType Windows -NamingScheme Fixed -NamingPrefix "$ServerName" -OrgName "$OrgName" -FullName "$FullName" -Domain "$domain" –DomainUsername "$DomainUser" –DomainPassword "$DomainPassword" -AdminPassword "$Admin_Password" #Modify the default network customization settings: $custSpec | Get-OSCustomizationNicMapping | Set-OSCustomizationNicMapping -IpMode UseStaticIP -IpAddress $IPAddress -SubnetMask $SubnetMask -Dns $DNS -DefaultGateway $DefaultGateway -Wins $WINS #Deploy a VM from a template using the newly created customization: New-VM -Name "$ServerName" -Template "$template" -VMHost "$vm_host" -datastore "$DatastoreName" | Set-VM -OSCustomizationSpec $custSpec -Confirm:$false #Turn it on Get-VM $ServerName | Get-NetworkAdapter | Set-NetworkAdapter -StartConnected:$true -confirm:$false Start-VM -VM $ServerName #Move Server cls write-host "************ Creating $ServerName; DO NOT CLOSE/STOP THIS SCRIPT! ************`n`n" -f yellow do { Get-ADComputer -identity $servername -ErrorAction SilentlyContinue write-host "." -nonewline -f red sleep 3 } until(Get-ADComputer -identity $servername -ErrorAction SilentlyContinue -warningaction silentlycontinue) $serverdn = (Get-ADComputer -identity $servername).distinguishedname Move-ADObject -identity "$serverdn" -TargetPath "$fullpath" #And we're done... cls write-host "$ServerName successfully created!" -f green write-host "Press any key to exit ..." -f red $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") exit } |
I hope this post is useful to you, and thank you for reading.
Download ZIP
[wpdm_file id=1]
Source Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
######################################## # Create VM # Created by Troy Ward ######################################## #Variables $dc = "dc1" # Local domain controller $VIServer = "vcenter1" # vCenter server $OrgName = "Company, Inc." $FullName = "I.T Infrastructure" $Domain = "domain.local" # Domain name $DomainUser = "DomainJoinUser" # Username for user that can join the domain $DomainPassword = "password" # Password for user that can join the domain $vm_host = "ESXI1" # Host that will host this VM $SubnetMask = "255.255.255.0" # Subnet Mask $DNS = "10.1.1.10,10.1.1.20" # DNS info $WINS = "10.1.1.10,10.1.1.20" # Leave blank if you do not use WINS $Admin_Password = "password" # Admin Password for new server $template = "Template" # Server Template name $fullpath = "OU=Servers,OU=location,DC=domain,DC=local" # OU that will hold the servers # No more variables $ui = (Get-Host).UI.RawUI $window_title = "Create VM" $ui.WindowTitle = "$window_title" $vm_host = "$vm_host.$domain" #Connect to Active Directory and load modules function ConnectAD { $session = New-PSSession -ComputerName "$dc" Invoke-Command -Session $session -ScriptBlock {Import-Module ActiveDirectory} Import-PSSession -Session $session -Module ActiveDirectory } function round($value, [MidpointRounding]$mode = 'AwayFromZero') { [Math]::Round( $value, $mode ) } #Load modules if(!(Get-PSSession | ?{$_.ComputerName -like "$dc*"}) -and $askoncepersessionmodules -ne 1) { $global:askoncepersessionmodules = 1 cls write-host "Connecting to Active Directory, and VIServer. Please wait..." -f yellow; add-pssnapin VMware.VimAutomation.Core ConnectAD connect-viserver $VIServer } #Setup Server info function SetupServer($error) { cls write-host "`n---------------------------------" -f yellow write-host "$window_title" -f green write-host "---------------------------------`n" -f yellow # If $error is not $null, display what the error is. if($error) { write-host "$error`n" -f red } # Gather server information $ServerName = (Read-Host "Server Name").ToUpper() $IPAddress = Read-Host "IP Address" $DefaultGateway = Read-Host "Default Gateway" #Create datastore list $datastores = get-datastore | Sort Name foreach($datastore in $datastores) { if($datastore -notlike "datastore*") { $spacefree = round ($datastore.FreeSpaceMB / 1024) $characters = $datastore | measure-object -character | select -expandproperty characters $maxcharacters = 18; $needed = $maxcharacters - $characters; $count = 1; $periods = $null; while($count -le $needed) { $periods = "$periods."; $count++; } if($spacefree -lt 100) { write-host "$datastore$periods " -nonewline -f red; write-host "$spacefree GB" -f red; } elseif($spacefree -lt 500) { write-host "$datastore$periods " -nonewline -f yellow; write-host "$spacefree GB" -f yellow; } else { write-host "$datastore$periods " -nonewline -f green; write-host "$spacefree GB" -f green; } } } # Which datastore do we want to select? $DatastoreName = (Read-Host "Datastore Name").ToUpper() if(!(get-datastore $DatastoreName -ErrorAction SilentlyContinue)) { $error = "Datastore $DatastoreName not found." SetupServer($error) } #Check our work if(!$ServerName -or !$IPAddress -or !$DefaultGateway -or !$DataStoreName) { $error = "Server Name, IP Address, Default Gateway, and Datastore Name cannot be left blank." SetupServer($error) } else { $result = $null $message = "`nAre you sure you want to create this server? ($ServerName; $IPAddress; $DefaultGateway; $DataStoreName)" $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes","" $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "" $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) $result = $host.ui.PromptForChoice($title, $message, $options, 0) if($result -eq 0) { Create-VM } else { SetupServer } } } function Create-VM { cls write-host "************ Creating $ServerName; DO NOT CLOSE/STOP THIS SCRIPT! ************" -f red #Create a simple customizations spec: $custSpec = New-OSCustomizationSpec -Type NonPersistent -ChangeSID -OSType Windows -NamingScheme Fixed -NamingPrefix "$ServerName" -OrgName "$OrgName" -FullName "$FullName" -Domain "$domain" –DomainUsername "$DomainUser" –DomainPassword "$DomainPassword" -AdminPassword "$Admin_Password" #Modify the default network customization settings: $custSpec | Get-OSCustomizationNicMapping | Set-OSCustomizationNicMapping -IpMode UseStaticIP -IpAddress $IPAddress -SubnetMask $SubnetMask -Dns $DNS -DefaultGateway $DefaultGateway -Wins $WINS #Deploy a VM from a template using the newly created customization: New-VM -Name "$ServerName" -Template "$template" -VMHost "$vm_host" -datastore "$DatastoreName" | Set-VM -OSCustomizationSpec $custSpec -Confirm:$false #Turn it on Get-VM $ServerName | Get-NetworkAdapter | Set-NetworkAdapter -StartConnected:$true -confirm:$false Start-VM -VM $ServerName #Move Server cls write-host "************ Creating $ServerName; DO NOT CLOSE/STOP THIS SCRIPT! ************`n`n" -f yellow do { Get-ADComputer -identity $servername -ErrorAction SilentlyContinue write-host "." -nonewline -f red sleep 3 } until(Get-ADComputer -identity $servername -ErrorAction SilentlyContinue -warningaction silentlycontinue) $serverdn = (Get-ADComputer -identity $servername).distinguishedname Move-ADObject -identity "$serverdn" -TargetPath "$fullpath" #And we're done... cls write-host "$ServerName successfully created!" -f green write-host "Press any key to exit ..." -f red $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") exit } SetupServer |