I have found a very simple deployment script (will probably add more complex stuff in there as I get a little better at PowerShell/PowerCli, pretty new at this right now).
I would like to add some more functions.
1. I have multiple templates in vCenter (Windows 2019, Windows 2016 and Windows 2012). When I run the script I would like to being able to choose from the different templates. These templates will also use different VM Customization Specifications named in the same way as the templates. In this script I only use one template and one Customization Specification. How can I add this function to add more template (OS versions)?
This is how it looks right now (may a bit messy I know):
#### USER DEFINED VARIABLES ############################################################################################
$Domain = "xxxxxxxxxxxxxxx" #AD Domain to join
$vCenter = "xxxxxxxxxxxxxxx" #vCenter to deploy VM
$Cluster = "xxxxxxxxxxxxxxx" #vCenter cluster to deploy VM
$VMTemplate = "Windows 2019 Server" #vCenter template to deploy VM
$CustomSpec = "Windows 2019 Server" #vCenter customization to use for VM
$Location = "Deploy" #Folderlocation in vCenter for VM
$DataStore = "xxxxxxxxxxxxxxx" #Datastore in vCenter to use for VM
$DiskStorageFormat = "EagerZeroedThick" #Diskformtat to use (Thin / Thick) for VM
$NetworkName = "xxxxxxxxxxxxxxx" #Portgroup to use for VM
$Memory = "8" #Memory of VM In GB
$CPU = "2" #number of vCPUs of VM
$DiskCapacity = "80" #Disksize of VM in GB
$SubnetLength = "xxxxxxxxxxxxxxx" #Subnetlength IP address to use (24 means /24 or 255.255.255.0) for VM
$GW = "xxxxxxxxxxxxxxx" #Gateway to use for VM
$IP_DNS = "xxxxxxxxxxxxxxx" #IP address DNS server to use
### FUNCTION DEFINITIONS ################################################################################################
Function Check-CustomizationStarted([string] $VM)
{
Write-Host "Verifying that Customization for VM $VM has started"
$i=60 #time-out of 5 min
while($i -gt 0)
{
$vmEvents = Get-VIEvent -Entity $VM
$startedEvent = $vmEvents | Where { $_.GetType().Name -eq "CustomizationStartedEvent" }
if ($startedEvent)
{
Write-Host "Customization for VM $VM has started"
return $true
}
else
{
Start-Sleep -Seconds 5
$i--
}
}
Write-Warning "Customization for VM $VM has failed"
return $false
}
Function Check-CustomizatonFinished([string] $VM)
{
Write-Host "Verifying that Customization for VM $VM has finished"
$i = 60 #time-out of 5 min
while($true)
{
$vmEvents = Get-VIEvent -Entity $VM
$SucceededEvent = $vmEvents | Where { $_.GetType().Name -eq "CustomizationSucceeded" }
$FailureEvent = $vmEvents | Where { $_.GetType().Name -eq "CustomizationFailed" }
if ($FailureEvent -or ($i -eq 0))
{
Write-Warning "Customization of VM $VM failed"
return $False
}
if ($SucceededEvent)
{
Write-Host "Customization of VM $VM Completed Successfully"
Start-Sleep -Seconds 30
Write-Host "Waiting for VM $VM to complete post-customization reboot"
Wait-Tools -VM $VM -TimeoutSeconds 300
Start-Sleep -Seconds 30
return $true
}
Start-Sleep -Seconds 5
$i--
}
}
Function Restart-VM([string] $VM)
{
Restart-VMGuest -VM $VM -Confirm:$false | Out-Null
Write-Host "Reboot VM $VM"
Start-Sleep -Seconds 60
Wait-Tools -VM $VM -TimeoutSeconds 300 | Out-Null
Start-Sleep -Seconds 10
}
function Add-Script([string] $script,$parameters=@(),[bool] $reboot=$false){
$i=1
foreach ($parameter in $parameters)
{
if ($parameter.GetType().Name -eq "String") {$script=$script.replace("%"+[string] $i,'"'+$parameter+'"')}
else {$script=$script.replace("%"+[string] $i,[string] $parameter)}
$i++
}
$script:scripts += ,@($script,$reboot)
}
Function Test-IP([string] $IP)
{
if (-not ($IP) -or (([bool]($IP -as [IPADDRESS])))) { return $true} else {return $false}
}
#### USER INTERACTIONS ##############################################################################################
cls
Write-host "Deploy Windows server" -foregroundcolor red
$Hostname = Read-Host -Prompt "Hostname"
If ($Hostname.Length -gt 15) {write-Host -ForegroundColor Red "$Hostname is an invalid hostname"; break}
$IP = Read-Host -Prompt "IP Address (press ENTER for DHCP)"
If (-not (Test-IP $IP)) {write-Host -ForegroundColor Red "$IP is an invalid address"; break}
$JoinDomainYN = Read-Host "Join Domain $Domain (Y/N)"
### READ CREDENTIALS ########################################################################################################
$User = "xxx"
$PasswordFile = "AESpassword.txt"
$KeyFile = "AES.key"
$key = Get-Content $KeyFile
$VIcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, (Get-Content $PasswordFile | ConvertTo-SecureString -Key $key)
### CONNECT TO VCENTER ##############################################################################################
Get-Module -ListAvailable VMware* | Import-Module | Out-Null
Connect-VIServer $vCenter -Credential $VIcred
$SourceVMTemplate = Get-Template -Name $VMTemplate
$SourceCustomSpec = Get-OSCustomizationSpec -Name $CustomSpec
### DEFINE POWERSHELL SCRIPTS TO RUN IN VM AFTER DEPLOYMENT ############################################################################################################
if ($IP) {
Add-Script "New-NetIPAddress -InterfaceIndex 2 -IPAddress %1 -PrefixLength %2 -DefaultGateway %3" @($IP, $SubnetLength, $GW)
Add-Script "Set-DnsClientServerAddress -InterfaceIndex 2 -ServerAddresses %1" @($IP_DNS) }
if ($JoinDomainYN.ToUpper() -eq "Y") {
Add-Script '$DomainUser = %1;
$DomainPWord = ConvertTo-SecureString -String %2 -AsPlainText -Force;
$DomainCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $DomainUser, $DomainPWord;
Add-Computer -DomainName %3 -Credential $DomainCredential' @("$Domain\$DomainAdmin",$DomainAdminPassword, $Domain) $true }
### DEPLOY VM ###############################################################################################################################################################
Write-Host "Deploying Virtual Machine with Name: [$Hostname] using Template: [$SourceVMTemplate] and Customization Specification: [$SourceCustomSpec] on cluster: [$cluster]"
New-VM -Name $Hostname -Template $SourceVMTemplate -ResourcePool $cluster -OSCustomizationSpec $SourceCustomSpec -Location $Location `
-Datastore $Datastore -DiskStorageFormat $DiskStorageFormat | Out-Null
Get-VM $Hostname | Get-NetworkAdapter | Set-NetworkAdapter -NetworkName $NetworkName -confirm:$false | Out-Null
Set-VM -VM $Hostname -NumCpu $CPU -MemoryGB $Memory -Confirm:$false | Out-Null
Get-VM $Hostname | Get-HardDisk | Where-Object {$_.Name -eq "Hard Disk 1"} | Set-HardDisk -CapacityGB $DiskCapacity -Confirm:$false | Out-Null
Write-Host "Virtual Machine $Hostname Deployed. Powering On"
Start-VM -VM $Hostname | Out-Null
if (-not (Check-CustomizationStarted $Hostname)) { break }; if (-not (Check-CustomizatonFinished $Hostname)) { break }
foreach ($script in $scripts)
{
Invoke-VMScript -ScriptText $script[0] -VM $Hostname -GuestCredential $VMLocalCredential | Out-Null
if ($script[1]) {Restart-VM $Hostname}
}
### END OF SCRIPT ##############################
Write-Host "Deployment of VM $Hostname finished"
If there is only 1 template or 1 OSCustomizationSpec, the variables $templates and $customizations will not be arrays.
In that case, the indexing ([$answer - 1]) takes 1 character from the string.
We can force the variables to be arrays, een if there is only 1 entry.
Like this
$templates = @(Get-Template | select -ExpandProperty Name)
$templates | ForEach-Object -Process {
$i++
Write-Host "$i $_"
}
$answer = Read-Host -Prompt "`nSelect Template (1-$($templates.Count))"
$SourceVMTemplate = $templates[$answer-1]
$i = 0
$customizations = @(Get-OSCustomizationSpec | select -ExpandProperty Name)
$customizations | ForEach-Object -Process {
$i++
Write-Host "$i $_"
}
$answer = Read-Host -Prompt "`nSelect the customization type (1-$($customizations.Count))"
$SourceCustomSpec = $customizations[$answer-1]
---------------------------------------------------------------------------------------------------------
Was it helpful? Let us know by completing this short survey here.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Instead of assigning a static value, you could do something like this
$CustomSpec = Get-OSCustomizationSpec | Out-GridView -OutputMode Single | select -ExpandProperty Name
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Yes, nice! But if I don´t want it to be OutGrid? Just want them numbered so I can select a number....."Ingrid" 🙂
You mean something along these lines?
$templates = Get-Template | select -ExpandProperty Name
$templates | ForEach-Object -Process {
$i++
Write-Host "$i $_"
}
$answer = Read-Host -Prompt "`nSelect the template (1-$($templates.Count))"
$VMTemplate = $templates[$answer-1]
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Yes exactly! Can I do the same to OSCustomizationSpec?
Sure, just change the Get-Template cmdlet to Get-OSCustomizationSpec.
And adapt the names of the variables of course.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
What have I missed?
I get:
New-VM : 2019-09-04 10:36:52 New-VM Could not find OSCustomizationSpec with name 'W'.
At C:\SCRIPTS\Deploy-VM.ps1:146 char:1
### CONNECT TO VCENTER ##############################################################################################
Get-Module -ListAvailable VMware* | Import-Module | Out-Null
Connect-VIServer $vCenter -Credential $VIcred
$i = 0
$templates = Get-Template | select -ExpandProperty Name
$templates | ForEach-Object -Process {
$i++
Write-Host "$i $_"
}
$answer = Read-Host -Prompt "`nSelect Template (1-$($templates.Count))"
$SourceVMTemplate = $templates[$answer-1]
$i = 0
$customizations = Get-OSCustomizationSpec | select -ExpandProperty Name
$customizations | ForEach-Object -Process {
$i++
Write-Host "$i $_"
}
$answer = Read-Host -Prompt "`nSelect the customization type (1-$($customizations.Count))"
$SourceCustomSpec = $customizations[$answer-1]
### DEFINE POWERSHELL SCRIPTS TO RUN IN VM AFTER DEPLOYMENT ############################################################################################################
if ($IP) {
Add-Script "New-NetIPAddress -InterfaceIndex 2 -IPAddress %1 -PrefixLength %2 -DefaultGateway %3" @($IP, $SubnetLength, $GW)
Add-Script "Set-DnsClientServerAddress -InterfaceIndex 2 -ServerAddresses %1" @($IP_DNS) }
if ($JoinDomainYN.ToUpper() -eq "Y") {
Add-Script '$DomainUser = %1;
$DomainPWord = ConvertTo-SecureString -String %2 -AsPlainText -Force;
$DomainCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $DomainUser, $DomainPWord;
Add-Computer -DomainName %3 -Credential $DomainCredential' @("$Domain\$DomainAdmin",$DomainAdminPassword, $Domain) $true }
### DEPLOY VM ###############################################################################################################################################################
Write-Host "Deploying Virtual Machine with Name: [$Hostname] using Template: [$SourceVMTemplate] and Customization Specification: [$SourceCustomSpec] on cluster: [$cluster]"
New-VM -Name $Hostname -Template $SourceVMTemplate -ResourcePool $cluster -OSCustomizationSpec $SourceCustomSpec -Location $Location -Datastore $Datastore -DiskStorageFormat $DiskStorageFormat | Out-Null
If there is only 1 template or 1 OSCustomizationSpec, the variables $templates and $customizations will not be arrays.
In that case, the indexing ([$answer - 1]) takes 1 character from the string.
We can force the variables to be arrays, een if there is only 1 entry.
Like this
$templates = @(Get-Template | select -ExpandProperty Name)
$templates | ForEach-Object -Process {
$i++
Write-Host "$i $_"
}
$answer = Read-Host -Prompt "`nSelect Template (1-$($templates.Count))"
$SourceVMTemplate = $templates[$answer-1]
$i = 0
$customizations = @(Get-OSCustomizationSpec | select -ExpandProperty Name)
$customizations | ForEach-Object -Process {
$i++
Write-Host "$i $_"
}
$answer = Read-Host -Prompt "`nSelect the customization type (1-$($customizations.Count))"
$SourceCustomSpec = $customizations[$answer-1]
---------------------------------------------------------------------------------------------------------
Was it helpful? Let us know by completing this short survey here.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
GREAT!! Thank you for all the help!!!
Would you please share your final script?