Release date: September 20th 2021
Welcome to my VMware Horizon series. In this session I will describe how I’ve automated the VMware Horizon Template creation and maintenance process. In my previous post: VMware Horizon – Windows 10 Desktop – From design to desktop pool, I described the manual procedure for doing this, which I find both time-consuming and tedious. Therefore, I was pleasantly surprised when I saw that VMware Digital Workspace Tech Zone had done the job and created a blog about this here: Using Automation to Create Optimized Windows Images for VMware Horizon VMs They’ve done a very thorough job and I used this as a guide when I did the set up myself. VMware have provided two scripts that helps automating VMware Horizon operations:
Using the Create Or Reset VMs script VMware has provided will work well enough, although, as my goal was to automate the complete process, I had to extend their script by adding the following features:
- When the template vm is created, the VM was quite large in size. I needed to make a routine that would export this to an OVA-file, delete the original template vm, import OVA in Thin-mode to vSphere and clean up and delete the OVA-file on local storage.
- Adjust Portgroup-setting on the imported vm, take a snapshot and push this to VMware Horizon
The workflow for this task will be as shown below:
Before I got to the point of actual scripting, I set up the MS WDS/MDT infrastructure as follows:
- Prepare Microsoft Windows Deployment Infrastructure:
I also had to setup VMware PowerCLI to be able to Automate VMware Horizon.
Once I had this in place, I could start creating my PowerCLI routines, but first I created the Credential Store XML-files to automate login to vSphere and Horizon, read more about the cmdlet here: New-VICredentialStoreItem
New-VICredentialStoreItem -User <user> -Password <user> -Host <server> -File C:\<your location.xml>
Export VM to OVA delete original VM:
Once the Create Or Reset VMs script has run, the VM is created and shut down. As show below the disk size is almost 40 GB.
Therefore I will have my script do the following procedures first.
- Connect to my vCenter server using my predefined Credential Store Item
- Remove any snapshots
- Disconnect any ISO-files
- Export the vm to an OVA file in the D:\Images folder on the Windows machine running the script. (Be sure to have enough disk space available!)
- Remove the VM from vSphere
$ExportDir = "D:\Images\" $viserver = "<FQDN vCenter Server>" $viuser = Get-VICredentialStoreItem -File "D:\scripts\vc-01_sso.xml" -host $viserver Connect-viserver -Server $viserver -User $viuser.user -Password $viuser.password Get-Snapshot $vm | Remove-Snapshot -confirm:$false Get-VM -Name $vm | Get-CDDrive | Set-CDDrive -NoMedia -confirm:$false Get-VM -Name $vm | Export-VApp -Destination $ExportDir -Format OVA -Force Get-VM -Name $vm | Remove-VM -DeletePermanently
Import from OVA-File and optionally change Portgroup
As with the export routine above, the routine below also demand that we are logged in to the vCenter server. This routine will import from the exported OVA file in thin format to vSphere. It will also make sure the VM gets connected to the correct port group (Optional).
$VMCluster = Get-Cluster -name "<vSphere Cluster Name>" $VMCluster | Get-VMHost $VMHOST = $VMCluster | Get-VMHost -Name <FQDN ESXi-host> $ovaFile = "D:\Images\ovafile.OVA" $FolderContainer = "<vSphere VM Folder>" $vmDatastore = Get-Datastore -Name "<Datastore Name>" # If using a DataStore Cluster: # $VMDataStoreCluster = Get-DatastoreCluster -Name "DatastoreCluster Name" $StorageFormat = "Thin" $vmDestination = "<Name of destination VM>" $VMHOST | Import-VApp -Source $ovaFile -Location $ClusterName -InventoryLocation $FolderContainer -Datastore $VMDatastore -DiskStorageFormat $StorageFormat -Name $VMDestination -Force $VMPortgroup = "<Portgroup Name>" Get-VM -Name $VMDestination | Get-NetworkAdapter | Set-NetworkAdapter -NetworkName $VMPortgroup -Confirm:$false
After the shrinking-operation, the VM have a much smaller footprint, only 12,8 GB, which is quite acceptable.
Delete OVA file from local storage, clone template to Horizon Template VM
In order to clean up, I first delete the OVA file from local storage:
Remove-item $ovaFile -Force
Next I will clone the imported VM to a “Horizon Template-VM”, as this will be used with the Horizon Desktop Pools. This new VM will be “date-stamped” so that I can keep track of when it was created.
$vmName_Prefix = "HZVM01" $vmName_Suffix = Get-Date -Format "dd/MM/yyyy" $Hz-Tpl = $vmName_Prefix+$vmName_Suffix $ResourcePool = Get-ResourcePool -Name FreLab $FolderContainer = "HZ-Templates" $SnapshotName = "MDTDeploy" New-VM -VM $VMDestination -Name $Hz-Tpl -ResourcePool $ResourcePool -Location $FolderContainer New-Snapshot -VM $Hz-Tpl -Name $SnapshotName
Delete OVA file from local storage, take snapshot and Push to VMware Horizon
The final part of my script do the following:
- Connects to to my Horizon Connection server using my predefined Credential Store Item
- Push the new VM and snapshot to my Desktop Pool
$HVServer = "<FQDN Horizon Connection Server>" $HVUser = Get-VICredentialStoreItem -File "D:\scripts\admin_hz.xml" -host $HVServer Connect-HVServer -Server $HVServer -User $HVUser.user -Password $HVUser.password $PoolName = "<VMware Horizon Desktop Pool Name>" Start-HVPool -SchedulePushImage -Pool $PoolName -LogoffSetting WAIT_FOR_LOGOFF -ParentVM $Hz-Tpl -SnapshotVM $SnapshotName
As I’m in no sense an expert in PowerCLI-scripting or programming, the above scripting most probably lack somewhat in finesse, but it gets the job done, and that is good enough for me. If it is helpful for anyone else, excellent, because that is why i published this. Using this automated setup, it is quite easy to do maintenance tasks, as it is only a matter of updating the task sequence with new versions etc.
- Day 2 Maintenance:
- Update VMware Tools
- Change Operating System
- Add new VMware Agents
VMware Digital Workspace Tech Zone: Using Automation to Create Optimized Windows Images for VMware Horizon VMs
Disclaimer: Every tips/tricks/posting I have published here, is tried and tested in different it-solutions. It is not guaranteed to work everywhere, but is meant as a tip for other users out there. Remember, Google is your friend and don’t be afraid to steal with pride! Feel free to comment below as needed.