Automate the building of your (Golden) Image on VMware using Packer and Chocolatey


Recently I set up a new VMWare Horizon environment in my homelab based on the following components:

VMware vCenter Server 7u1
VMware ESXi 7u1
Vmware Horizon Connection Server 8
VMware App Volumes 4.2
VMware DEM 2009

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.

In this blog article I want to show how you can automatically build an image on a VMware platform using Packer. Packer can also be used on other platforms like Azure, AWS, Hyper-V or VirtualBox. I also want to provide this image with the VMWare Horizon agents and run VMware OSOT as last step before turning off the Virtual Machine. These will also be installed automatically within the image using an on-premise Chocolatey environment.

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


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"


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": [
		"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": [
			"type": "windows-restart",
			"restart_timeout": "30m"
			"type": "powershell",
			"scripts": [
	"sensitive-variables": [



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


	<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">
            <PathAndCredentials wcm:action="add" wcm:keyValue="A">


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:

Roderik de Block


  1. 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.

  2. Nice post. The secrets are in plain text. Look at for example Hasicorp Vault for storing the secrets secure in the Packer config files.

Leave a Reply

Your email address will not be published. Required fields are marked *