Automating VDI Template Creation with VMware Code Stream and HashiCorp Packer – Part 1: Building Windows

In previous consultancies I’ve worked, it was common practice to frequently patch corporate VDI templates. Whilst continuous patching is a good thing, it is quite often laborious. Any attempt to automate this is seen as difficult and organizations often give up and continue with manual processes. Here I will show how it can be done using Code Stream and HashiCorp Packer.

Other posts in this series:


In HobbitCloud we use a mixture of Windows and Linux VDI desktops. Whilst the latter is my desktop of choice, some users still prefer Redmond’s offering.

To offer the most cost-efficient solution in terms of storage and manageability, we use linked-clone desktops with applications provided by VMware AppVolumes. This enables us to provision a clean base image, with all applications installed in an AppStack. While not every application is suitable for an AppStack (I would always put Microsoft Office/365 into the base image), fortunately the standard apps we use are.

Therefore all we need to build is a Windows 10 desktop, patched with the latest updates, with the AppVolumes, Dynamic Environment Manager and VMware Horizon Agents installed. Once we become familiar with how to build the solution in a scripted fashion, we can leverage VMware’s continuous delivery tool, Code Stream, to automate the process.

What we’ll need:

Please note: be sure to choose the correct VMware Tools and software agents for your Horizon environment. Consult the support matrix where necessary.

Solution Overview

Here is a high-level plan for how the solution will work:

Getting Started

Create a folder with a meaningful name in your filesystem. I have gone with $HOME/git/packer/windows-10. This will be our root folder.

Download Packer 1.4.2 and unzip it to the root folder. Also download the JetBrains Packer Builder for vSphere (link above) and save it to the same folder.

Next, create a JSON file called windows-10.json with the following and save it to the root folder:

    "builders": [
        "type": "vsphere-iso",

        "vcenter_server":      "{{user `vcenter_server`}}",
        "username":            "{{user `username`}}",
        "password":            "{{user `password`}}",
        "insecure_connection": "true",

        "vm_name": "Windows 10 (LC) (PILOT)",
        "datastore": "{{user `datastore`}}",
        "create_snapshot": "false",
        "cluster": "{{user `cluster`}}",
        "network": "{{user `network`}}",
        "boot_order": "disk,cdrom",

        "vm_version":       15,  
        "guest_os_type": "windows9_64Guest",

        "communicator": "winrm",
        "winrm_username": "{{user `winrm_username`}}",
        "winrm_password": "{{user `winrm_password`}}",

        "CPUs":             2,
        "RAM":              8192,
        "RAM_reserve_all":  true,

        "disk_controller_type":  "pvscsi",
        "disk_size":        51200,
        "disk_thin_provisioned": true,

        "network_card": "vmxnet3",

        "iso_paths": [
        "[{{user `datastore_iso`}}] en-gb_windows_10_business_editions_version_1903_x64_dvd_4170a06f.iso",
        "[{{user `datastore_iso`}}] VMware-tools-windows-10.3.10-12406962.iso"

        "floppy_files": [
        "floppy_img_path": "[{{user `datastore`}}] Floppies/pvscsi-Windows8.flp"

    "provisioners": [
        "type": "powershell",
        "inline": [
          "Get-AppXPackage -AllUsers | Where {($ -notlike \"Photos\") -and ($_.Name -notlike \"Calculator\") -and ($_.Name -notlike \"Store\")} | Remove-AppXPackage -ErrorAction SilentlyContinue",
          "Get-AppXProvisionedPackage -Online | Where {($_.DisplayName -notlike \"Photos\") -and ($_.DisplayName -notlike \"Calculator\") -and ($_.DisplayName -notlike \"Store\")} | Remove-AppXProvisionedPackage -Online -ErrorAction SilentlyContinue"     
        "type": "powershell",
        "scripts": [

Things to note:

  • Line 11: here’s where you specify what you want the virtual machine to be named (substitute accordingly)
  • Lines 25-26 and 30: here’s where you size your VM. 2 vCPUs, 8GB RAM and 50GB disk is the HobbitCloud default
  • Lines 36-37: the Windows and VMware Tools ISO need to exist on a datastore of your choosing
  • Line 42: this is where we will store our scripts (more on this later)
  • Lines 51-52: some PowerShell commands for hacking out all the unnecessary Windows garbage. Delete as necessary.
  • Line 58 onwards: these call out the scripts we will be using. Feel free to include/exclude as necessary. I would remove them all at first, get the solution working, then add them one at a time.

Create a JSON file named variables.json and add the following (substitute accordingly):

    "cluster": "Management",
    "network": "VLAN70",
    "winrm_username": "Administrator",
    "winrm_password": "VMware1!"

Things to note:

  • Lines 9-10: these need to match what is defined in Autounattend.xml file (see next section)

Windows 10

Now we need to start defining how our Windows 10 box will be built. Before we do that, upload the Windows 10 and VMware Tools ISOs (defined in windows-10.json) to the datastore defined in variables.json. As I configure my Windows desktop to use the pvscsi driver, you will also need to upload the floppy image for this too. This is defined at line 43 of windows-10.json, but if you don’t use it you can ignore that and hack it out.

Create a folder under the root folder called setup. Download the Autounattend.xml file from here and save it to this folder.

Create a command script called vmtools.cmd in the setup folder using:

@rem Silent mode, basic UI, no reboot
e:\setup64 /s /v "/qb REBOOT=R"

Lastly, create a PowerShell script called setup.ps1 with the following and save it to the setup folder:

$ErrorActionPreference = "Stop"

# Switch network connection to private mode
# Required for WinRM firewall rules
$profile = Get-NetConnectionProfile
Set-NetConnectionProfile -Name $profile.Name -NetworkCategory Private

# Enable WinRM service
winrm quickconfig -quiet
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service/auth '@{Basic="true"}'

# Reset auto logon count
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name AutoLogonCount -Value 0

You should now have enough to stand up the base build.

Try it…

In the root folder, run the following:

packer build -force -var-file variables.json windows-10.json

If all goes well, then you should get some green text appearing in your terminal window:

Coming in spicy...

Coming up

In part 2 of this series will install our VDI agents, before moving on to automate the entire solution in part 3.

3 thoughts on “Automating VDI Template Creation with VMware Code Stream and HashiCorp Packer – Part 1: Building Windows

  1. Pingback: vToolbelt - October 2019 - Cybersylum

  2. Pingback: Newsletter: October 5, 2019 – Notes from MWhite

  3. Pingback: Automating VDI Template Creation with VMware Code Stream and HashiCorp Packer – Part 2: Installing the VDI Agents | virtualhobbit

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.