I already blogged about pushing a new image using the Python module for Horizon but I decided it was time to also have a reusable script that is able to push a new image using powershell and the rest api for Horizon. The script that I created has 10(!) arguments of which 6 are required:
- ConnectionServerURL: https://server.domain.dom
- vCenterURL: https://vcenter.domain.dom
- DataCenterName: Name of the datacenter the source VM resides in
- BaseVMName : name of the source VM
-
BaseSnapShotName: name of the source Snapshot
-
DesktopPoolName: name of the source Desktop Pool to apply the snapshot to
The datacenter name is required as that’s an requirement to grab the Source VM details.
The optional arguments are:
- Credentials: PSCRedential object (get-credential for example) when not provided it will ask for user and password. The user should also contain the domain i.e. domain\user
- StoponError: $true or $false depending on if you want to stop on errors, defaults to $true if not provided
- logoff_policy: Optional WAIT_FOR_LOGOFF or FORCE_LOGOFF depending on the logoff policy you want
- Scheduledtime: [DateTime] object in case you want to push for the future
The script itself was fairly easy to create, from the api explorer it was easy what id’s I needed and it was a matter of working back so I had all of them. In the end the hardest part was getting the scheduling to work. I used the (standard?) 10 digit epoch that most people use but it turns out that VMware wanted to schedule it very exactly in milliseconds! For the rest I used the well known functions for authentication but not my generic function for filtering or pagination as almost none of the api calls I use in this script support that.
<# .SYNOPSIS Pushes a new Golden Image to a Desktop Pool .DESCRIPTION This script uses the Horizon rest api's to push a new golden image to a VMware Horizon Desktop Pool .EXAMPLE .\Horizon_Rest_Push_Image.ps1 -ConnectionServerURL https://pod1cbr1.loft.lab -Credentials $creds -vCenterURL "https://pod1vcr1.loft.lab" -DataCenterName "Datacenter_Loft" -baseVMName "W21h1-2021-09-08-15-48" -BaseSnapShotName "Demo Snapshot" -DesktopPoolName "Pod01-Pool02" .PARAMETER Credential Mandatory: No Type: PSCredential Object with credentials for the connection server with domain\username and password. If not supplied the script will ask for user and password. .PARAMETER ConnectionServerURL Mandatory: Yes Default: String URL of the connection server to connect to .PARAMETER vCenterURL Mandatory: Yes Username of the user to look for .PARAMETER DataCenterName Mandatory: Yes Domain to look in .PARAMETER BaseVMName Mandatory: Yes Domain to look in .PARAMETER BaseSnapShotName Mandatory: Yes Domain to look in .PARAMETER DesktopPoolName Mandatory: Yes Domain to look in .PARAMETER StoponError Mandatory: No Boolean to stop on error or not .PARAMETER logoff_policy Mandatory: No String FORCE_LOGOFF or WAIT_FOR_LOGOFF to set the logoff policy. .PARAMETER Scheduledtime Mandatory: No Time to schedule the image push in [DateTime] format. .NOTES Minimum required version: VMware Horizon 8 2012 Created by: Wouter Kursten First version: 03-11-2021 .COMPONENT Powershell Core #> [CmdletBinding()] param ( [Parameter(Mandatory=$false, HelpMessage='Credential object as domain\username with password' )] [PSCredential] $Credentials, [Parameter(Mandatory=$true, HelpMessage='FQDN of the connectionserver' )] [ValidateNotNullOrEmpty()] [string] $ConnectionServerURL, [parameter(Mandatory = $true, HelpMessage = "URL of the vCenter to look in i.e. https://vcenter.domain.lab")] [ValidateNotNullOrEmpty()] [string]$vCenterURL, [parameter(Mandatory = $true, HelpMessage = "Name of the Datacenter to look in.")] [ValidateNotNullOrEmpty()] [string]$DataCenterName, [parameter(Mandatory = $true, HelpMessage = "Name of the Golden Image VM.")] [ValidateNotNullOrEmpty()] [string]$BaseVMName, [parameter(Mandatory = $true, HelpMessage = "Name of the Snapshot to use for the Golden Image.")] [ValidateNotNullOrEmpty()] [string]$BaseSnapShotName, [parameter(Mandatory = $true, HelpMessage = "Name of the Desktop Pool.")] [ValidateNotNullOrEmpty()] [string]$DesktopPoolName, [parameter(Mandatory = $false, HelpMessage = "Name of the Desktop Pool.")] [ValidateNotNullOrEmpty()] [bool]$StoponError = $true, [parameter(Mandatory = $false, HelpMessage = "Name of the Desktop Pool.")] [ValidateSet('WAIT_FOR_LOGOFF','FORCE_LOGOFF', IgnoreCase = $false)] [string]$logoff_policy = "WAIT_FOR_LOGOFF", [parameter(Mandatory = $false, HelpMessage = "DateTime object for the moment of scheduling the image push.Defaults to immediately")] [datetime]$Scheduledtime ) if($Credentials){ $username=($credentials.username).split("\")[1] $domain=($credentials.username).split("\")[0] $password=$credentials.password } else{ $credentials = Get-Credential $username=($credentials.username).split("\")[1] $domain=($credentials.username).split("\")[0] $password=$credentials.password } $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password) $UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) function Get-HRHeader(){ param($accessToken) return @{ 'Authorization' = 'Bearer ' + $($accessToken.access_token) 'Content-Type' = "application/json" } } function Open-HRConnection(){ param( [string] $username, [string] $password, [string] $domain, [string] $url ) $Credentials = New-Object psobject -Property @{ username = $username password = $password domain = $domain } return invoke-restmethod -Method Post -uri "$ConnectionServerURL/rest/login" -ContentType "application/json" -Body ($Credentials | ConvertTo-Json) } function Close-HRConnection(){ param( $accessToken, $ConnectionServerURL ) return Invoke-RestMethod -Method post -uri "$ConnectionServerURL/rest/logout" -ContentType "application/json" -Body ($accessToken | ConvertTo-Json) } try{ $accessToken = Open-HRConnection -username $username -password $UnsecurePassword -domain $Domain -url $ConnectionServerURL } catch{ throw "Error Connecting: $_" } $vCenters = Invoke-RestMethod -Method Get -uri "$ConnectionServerURL/rest/monitor/v2/virtual-centers" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken) $vcenterid = ($vCenters | where-object {$_.name -like "*$vCenterURL*"}).id $datacenters = Invoke-RestMethod -Method Get -uri "$ConnectionServerURL/rest/external/v1/datacenters?vcenter_id=$vcenterid" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken) $datacenterid = ($datacenters | where-object {$_.name -eq $DataCenterName}).id $basevms = Invoke-RestMethod -Method Get -uri "$ConnectionServerURL/rest/external/v1/base-vms?datacenter_id=$datacenterid&filter_incompatible_vms=false&vcenter_id=$vcenterid" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken) $basevmid = ($basevms | where-object {$_.name -eq $baseVMName}).id $basesnapshots = Invoke-RestMethod -Method Get -uri "$ConnectionServerURL/rest/external/v1/base-snapshots?base_vm_id=$basevmid&vcenter_id=$vcenterid" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken) $basesnapshotid = ($basesnapshots | where-object {$_.name -eq $BaseSnapShotName}).id $desktoppools = Invoke-RestMethod -Method Get -uri "$ConnectionServerURL/rest/inventory/v1/desktop-pools" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken) $desktoppoolid = ($desktoppools | where-object {$_.name -eq $DesktopPoolName}).id $startdate = (get-date -UFormat %s) $datahashtable = [ordered]@{} $datahashtable.add('logoff_policy',$logoff_policy) $datahashtable.add('parent_vm_id',$basevmid) $datahashtable.add('snapshot_id',$basesnapshotid) if($Scheduledtime){ $starttime = get-date $Scheduledtime $epoch = ([DateTimeOffset]$starttime).ToUnixTimeMilliseconds() $datahashtable.add('start_time',$epoch) } $datahashtable.add('stop_on_first_error',$StoponError) $json = $datahashtable | convertto-json Invoke-RestMethod -Method Post -uri "$ConnectionServerURL/rest/inventory/v1/desktop-pools/$desktoppoolid/action/schedule-push-image" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken) -body $json
I use it like this for example:
D:\GIT\Various_Scripts\Horizon_Rest_Push_Image.ps1 -ConnectionServerURL https://pod1cbr1.loft.lab -vCenterURL "https://pod1vcr1.loft.lab" -DataCenterName "Datacenter_Loft" -baseVMName "W21h1-2021-09-08-15-48" -BaseSnapShotName "Demo Snapshot" -DesktopPoolName "Pod01-Pool01" -logoff_policy WAIT_FOR_LOGOFF -StoponError $true -Scheduledtime ((get-date).AddMinutes(75))
Except for the question for username and password (in this case) there’s no response
and the push image is scheduled 75 minutes in the future
As always the script itself is also available at Github HERE.
Next time I’ll add more error handling and update the script to also push images to RDS farms 🙂