Why?
Recently I set up a new VMWare Horizon environment in my homelab based on the following components:
This of course also means that a new Golden Image must be created for the Windows 10 VDI desktops. I thought it would be nice to have the image built up automatically with relatively new tooling for me. In the past I have already worked with Microsoft SCCM (Endpoint Manager) and PowerShell to automate this process as you can see in this post.
Let’s start, and install Packer
First, we need to install packer on the machine from which we want to perform the deployment. This is quite simple and we do it with the help of Chocolatey.
You can install Chocolatey with this one-liner in PowerShell:
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
Once Chocolatey is installed we can install packer with the following command. I used version 1.7 during this setup.
choco install packer --version 1.7 -y
Chocolatey On-Premise
In order to deploy the horizon and OSOT packages on premise to the new image to be built, we also need to deploy an on-premise chocolatey server. I deployed this Chocolatey server using the PowerShell script on this site: https://docs.chocolatey.org/en-us/guides/organizations/set-up-chocolatey-server
But it is als possible to install this server with:
- Ansible
- Puppet
- Chef
- Manually.
After installing the chocolatey server it is possible to install chocolatey on your endpoints by pointing to your own chocolatey server:
Set-ExecutionPolicy Bypass -Scope Process -Force ; iex ((New-Object System.Net.WebClient).DownloadString('http://<your server>/install.ps1'))
After installing this chocolatey server we have to package applications so that they can be deployed through Chcolatey.
Packaging Agents and OSOT
My colleagues from RawWorks have made a very nice tool for this. A demonstration video and a download are available here: https://www.rawworks.nl/home/downloads/
When the Agents are packaged, they can be installed using the commands below.
choco install <Your_App> -s http://<Your_Server>/chocolatey -y
Building the first image
To make an image with Packer a number of things are required. We need to create the following files:
- Variables.JSON –> Contains all the variables
- Windows10.JSON –> Contains the configuration of the Virtual Machine and determines the sequence of the steps we need to create the machine fully automated
- autonunattend.xml –> Answer file to create the Windows 10 image
- PowerShell, CMD, BAT files –> Install features, Windows Updates, (Chocolatey) applications, applying settings, enabling WINRM on the endpoint so packer is able to communicate
Variables.JSON
Edit this file with your settings and save it. We will need it later!
{"vsphere-server": "<Your_Server>",
"vsphere-user": "administrator@vsphere.local",
"vsphere-password": "<Your PassWord>",
"vsphere-datacenter": "<Your_DataCenter>",
"vsphere-cluster": "<Your_Cluster>",
"vsphere-network": "<Your_NetWork>",
"vsphere-datastore": "<Your_DataStore>",
"vsphere-folder": "<Your_VM_Folder>",
"winrm_username": "Admin",
"winadmin-password": "P@ssw0rd",
"vm-name": "<Your_VM_Name>",
"vm-cpu-num": "2",
"vm-mem-size": "4096",
"vm-disk-size": "40960",
"winadmin-password": "P@ssw0rd",
"os_iso_path": "[Your_Datastore] Win10_20H2_v2_Dutch_x64.iso",
"winrm_username": "Admin"
}
Windows10.JSON
This file consists of 2 parts. The first part “builder” consists of the configuration of the virtual machine, mounts the necessary files in a floppy drive and contains the usernames and passwords for the Windows 10 image and the winrm communication. The second part takes place after the installation of the image, such as Windows Updates and the installation of applications, drivers and agents.
{
"builders": [{
"CPUs": "{{user `vm-cpu-num`}}",
"RAM": "{{user `vm-mem-size`}}",
"RAM_reserve_all": true,
"cluster": "{{user `vsphere-cluster`}}",
"communicator": "winrm",
"create_snapshot": "true",
"datacenter": "{{user `vsphere-datacenter`}}",
"datastore": "{{user `vsphere-datastore`}}",
"disk_controller_type": "lsilogic-sas",
"firmware": "bios",
"floppy_files": [
"./autounattend.xml",
"./scripts/disable-network-discovery.cmd",
"./scripts/enable-rdp.cmd",
"./scripts/enable-winrm.ps1",
"./scripts/install-vm-tools.cmd",
"./scripts/set-temp.ps1",
"./scripts/microsoft-updates.ps1",
"./scripts/win-updates.ps1",
"./scripts/choco.ps1",
"./scripts/disable-screensaver.ps1"
],
"folder": "{{user `vsphere-folder`}}",
"guest_os_type": "windows9_64Guest",
"insecure_connection": "true",
"iso_paths": [
"{{user `os_iso_path`}}",
"[] /vmimages/tools-isoimages/windows.iso"
],
"network_adapters": [{
"network": "{{user `vsphere-network`}}",
"network_card": "vmxnet3"
}],
"password": "{{user `vsphere-password`}}",
"storage": [{
"disk_size": "{{user `vm-disk-size`}}",
"disk_thin_provisioned": true
}],
"type": "vsphere-iso",
"username": "{{user `vsphere-user`}}",
"vcenter_server": "{{user `vsphere-server`}}",
"vm_name": "{{user `vm-name`}}",
"winrm_insecure": "true",
"winrm_password": "{{user `winadmin-password`}}",
"winrm_use_ssl": "true",
"winrm_username": "{{user `winrm_username`}}"
}],
"provisioners": [{
"type": "windows-restart"
},
{
"type": "file",
"source": "./scripts/windowsupdate.ps1",
"destination": "c:\\temp\\build\\scripts\\windowsupdate.ps1"
},
{
"type": "powershell",
"scripts": [
"./scripts/packer_windowsupdate.ps1"
]
},
{
"type": "windows-restart",
"restart_timeout": "30m"
},
{
"type": "powershell",
"scripts": [
"./scripts/choco.ps1"
]
}
],
"sensitive-variables": [
"vsphere_password",
"winadmin_password"
]
}
autonunattend.xml
On my github you can find the autounattend file I created. You can modify this one, or create one yourself. If you don’t know how to do it, here is a good description:
The following items are controlled in this file:
- Language (Setup/Ui/Input…..)
- Diskconfiguration (partitions …)
- Setting an Admin account & Password
- Autologon
- First Logon Commands (CMD,BAT en PowerShell files which are directly after logging in the first time by AutoLogon)
Putting it all together!
When the above has all been set up, it is finally time to start building the image. With this command you kick off the installation process. The Windows 10 image will be installed with Windows Updates all the Horizon Agents and at the VMware OS Optimization Tool will seal the image. At last the VM will shut down and there will be a snapshot created by packer. The VM is ready to deploy to your Horizon Desktop Pool!!!!
packer build -var-file .\variables.json .\windows10.json
In my case the total deployment of Windows 10 with updates, Horizon Agents and OSOT took only 34 and 21 seconds.
Check te files on GitHub: https://github.com/roderikdeblock/Packer
Update (april 2nd 2021)
As you can see on my GitHub there is this folder (HCL-UEFI-PVSCI):
I created this folder because I made the following enhancements:
- HCL instead of JSON. (As of version 1.7.0, HCL2 support is no longer in beta and is the preferred way to write Packer configuration(s) instead of using JSON Files. Source: https://www.packer.io/guides/hcl)
Instead of 2 JSON files I am using one HCL file which contains all the variables, builders and provisioners. You only have to enter your variables to make it work.
- UEFI boot instead of BIOS
The Autounattend.xml in this folder is prepared to be used for an UEFI installation. I ran into the issue that my Windows 10 ISO was not booting automatically. The “Press any key to boot from CD or DVD” prompt had to be removed.
I fixed this by using this guide: Removing “press any key” prompts for GPT/UEFI Windows install automation (taylor.dev)
- PVPSCSI instad of LSILOGIC
To use PVSCSI in the image the PVSCI driver has to be loaded during startup of the Virtual Machine. I fixed this by putting this drivers in the ISO directory on my datastore. The autounattend.xml also have to be edited to let this work as you can see in the screenshot below. After these actions this driver is mounted as a floppy image.
HCL – PVSCI Driver
Autounattend.xml
</component>
<component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="A">
<Path>B:\</Path>
</PathAndCredentials>
</DriverPaths>
When everything is in place … it is time to build the image using this command:
packer build .\windows10.pkr.hcl
Screenshots of building image with packer with JSON files:
Great post! Have some minor feedback, it is recommended to move to the HCL2 format instead of JSON, see the following note at the Packer documentation:
Note: Packer version 1.5.0 introduced support for HCL2 templates as a beta feature. As of version 1.7.0, HCL2 support is no longer in beta and is the preferred way to write Packer configuration(s).
Source: https://www.packer.io/guides/hcl
Hi Ryan, thanks for the feedback. I am aware of that and I am already working on HCL2 and if I have gotten it all working to my liking then I will definitely update this post.
Nice post. The secrets are in plain text. Look at for example Hasicorp Vault for storing the secrets secure in the Packer config files.