VMware Cloud Community
sejohlun
Contributor
Contributor
Jump to solution

Simple Deploy VM Script

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"

0 Kudos
1 Solution

Accepted Solutions
LucD
Leadership
Leadership
Jump to solution

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

$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]

---------------------------------------------------------------------------------------------------------

Was it helpful? Let us know by completing this short survey here.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

View solution in original post

9 Replies
LucD
Leadership
Leadership
Jump to solution

Instead of assigning a static value, you could do  something like this

$VMTemplate = Get-Template | Out-GridView -OutputMode Single | Select -ExpandProperty Name

$CustomSpec = Get-OSCustomizationSpec | Out-GridView -OutputMode Single | select -ExpandProperty Name


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
sejohlun
Contributor
Contributor
Jump to solution

Yes, nice! But if I don´t want it to be OutGrid? Just want them numbered so I can select a number....."Ingrid" 🙂

0 Kudos
LucD
Leadership
Leadership
Jump to solution

You mean something along these lines?

$i = 0

$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

0 Kudos
sejohlun
Contributor
Contributor
Jump to solution

Yes exactly! Can I do the same to OSCustomizationSpec?

0 Kudos
LucD
Leadership
Leadership
Jump to solution

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

0 Kudos
sejohlun
Contributor
Contributor
Jump to solution

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

0 Kudos
LucD
Leadership
Leadership
Jump to solution

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

$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]

---------------------------------------------------------------------------------------------------------

Was it helpful? Let us know by completing this short survey here.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

sejohlun
Contributor
Contributor
Jump to solution

GREAT!! Thank  you for all the help!!!

0 Kudos
mfarooq1
Contributor
Contributor
Jump to solution

Would you please share your final script?

0 Kudos