Managing Windows Server VM templates with Packer - Part 1

Page content

Introduction

We all know the immense pain of managing Windows Server VM templates, regardless of the platform you’re using. Sure, you can build them once then update them manually on a schedule. However, it’s tedious to document and even worse to execute, making sure the template is identical every time (except for your new updates of course).

In my experience, you also have to maintain multiple versions and editions of Windows Server. For example, Windows Server 2008 R2 with Service Pack 1 both Standard and Datacenter right through to 2016 Standard and Datacenter with the latest Cumulative Updates. I came from a VMware Cloud Service Provider environment where having standardised, easy to manage and consume templates is the goal. However, this post can apply to any on-premises vSphere or vCloud Director environment (both as a tenant and as a provider).

I don’t know about you, but these are/were the high level steps you’d have to perform manually when creating or updating a VM template.

New Templates

  1. Find the ISO for the Operating System you want
  2. Create a new VM, removing all unnecessary virtual hardware
  3. Boot the VM, install Guest OS.
  4. Install VMware Tools.
  5. Change the vNIC to VMXNET3.
  6. For the experienced, spend a few minutes re-jigging the SCSI controller from LSI to Paravirtual. For the unexperienced, log an SR with VMware when the VM doesn’t boot after changing the controller.
  7. Install all OS Updates (don’t forget the incalculable number of reboots).
  8. Make anywhere from 1-100 tweaks, configuration changes, renames, resets, etc. that align to your business requirements.
  9. Shutdown the machine.
  10. Convert the VM to a VM template

Existing Templates

Microsoft patches are released every month. Do you update your templates every month? Or every 6-12 months because of how painful the process can be?

  1. Convert the VM template to a VM.
  2. Boot the VM.
  3. Attach it to a network.
  4. Check for and install however many updates you need to install.
  5. Update VMware Tools.
  6. Check and re-apply all guest OS customisations that should be there but were reverted due to an update.
  7. Shutdown the VM.
  8. Convert the VM back to a VM template.

I hated doing this. Especially if different people ran through the task every time, you might end up with differing customisations, or missed configs, each time the template was updated. And if the template was done manually, you could never keep a decent track of what changes are being made. Not to mention versioning of your config changes.

In this post series, I want to introduce you to Packer, get the right bits downloaded, and write your first template. With Packer, we can change our VM template management process from the above, to this:

  1. Execute “packer build”.
  2. Packer file is read.
  3. Packer builds a VM template with all the details of a Packer file.
  4. Packer publishes the new VM template to vSphere.

Look at that, you write your Packer file once, and every single time you run the build you’ll get the same VM template every time.

I’ll also show you how to get the automatically built templates into your environment, and finally, I’ll give you my Packer templates as a starting point. There are plenty of Packer templates already out there that you can use if you wish, I’m just hoping to add to the pile with my templates and scripts in the event they help you in one form or another.

This post covers what Packer is, what Packer templates look like, and getting started with your first template.

What is Packer?

Taken from Hashicorp’s own page:

Packer is an open source tool for creating identical machine images for multiple platforms from a single source configuration. Packer is lightweight, runs on every major operating system, and is highly performant, creating machine images for multiple platforms in parallel. Packer does not replace configuration management like Chef or Puppet. In fact, when building images, Packer is able to use tools like Chef or Puppet to install software onto the image.

A machine image is a single static unit that contains a pre-configured operating system and installed software which is used to quickly create new running machines.

Packer uses version controlled definition files to build, configure and prepare virtual machine images or “VM templates”. Packer supports building these images on many platforms (referred to as “Builders”) directly such as VMware Workstation/Fusion and vSphere and a whole lot more.

The Builder creates the VM, but what customises it? Something called a Provisioner. These “Provisioners” can be used to communicate with the guest OS of the image you’ve defined. You can do things like copy files to the guest or run scripts. These “Provisioners” are the workhorses that’ll tweak your template to precisely what you want.

Once the template has been created (with the “Builder”) and customised (with the “Provisioners”), you then need to publish/output it with a “Post-Processor”. A Post-Processor can let you push the template from the hypervisor you’re building on to an available endpoint such as vSphere or AWS to be consumed by your users or fellow administrators.

Why use Packer? Well, you can track all of your VM template configurations and build steps in a text file checked into a code repository, allowing you to track exactly what your template builds are doing. Imagine a change is made to a Packer definition or a PowerShell script you use during template builds and it ends up breaking your template or introducing a vulnerability. With this model, you’ll know the exact version that change was made and can easily revert the change and have Packer rebuild the template.

Here’s a sample Packer file that shows the use of a Builder that detects your local version of VMware Workstation/Fusion, builds a VM, runs a few scripts in the guest, and uploads the VM to a vSphere environment. Copying and pasting this straight to your local workstation won’t work, certain values have been anonymised.

{
    "builders": [
      {
        "type": "vmware-iso",
        "name": "WIN2019",
        "communicator": "winrm",
        "winrm_username": "administrator",
        "winrm_password": "password",
        "winrm_timeout": "4h",
        "disable_vnc": true,
        "headless": false,
        "iso_url": "URL-to-ISO",
        "iso_checksum_type": "md5",
        "iso_checksum": "checksum",
  
        "boot_wait": "5s",
        "shutdown_command": "shutdown /s /t 10 /f /d p:4:1 /c \"Packer Shutdown\"",
  
        "vm_name": "WIN2019",
        "guest_os_type": "windows8srv-64",
        "version": "13",
        "disk_size": 61440,
        "disk_type_id": 0,
        "vmdk_name": "WIN2019",
        "vmx_data": {
          "numvcpus": 2,
          "memsize": 2048
        },
        "floppy_files": [
          "./Autounattend.xml",
        ]
      }
    ],
    "provisioners": [
      {
        "type": "powershell",
        "scripts": [
          "./scripts/enable-ms-updates.ps1",
          "./scripts/set-power-mgmt.ps1",
          "./scripts/delete-defrag-schedule.ps1",
          "./scripts/visual-best-performance.ps1"
        ]
      }
    ],
    "post-processors": [
        {
            "type": "vsphere",
            "cluster": "mycluster",
            "datacenter": "mydatacenter",
            "datastore": "template-datastore",
            "host": "vcenterserver-fqdn",
            "password": "password",
            "username": "username",
            "vm_name": "WIN2019"
        }
    ]
  }

I’ve added a few additional options that are available for this Builder, but for the most part, you can have a minimalist Packer file that creates a VM, installs an operating system from an ISO, shuts it down and places it lovingly on your local disk as a folder of VMX and VMDK’s. It’s up to you how far you go customising the operating system after installation, which you can also do with Packer.

Let’s move on and get into it.

Packer requirements and bits

To start playing with Packer, you’re going to need a few things:

  • Packer - Check the Install Guide.
  • A supported Builder. For this series I’ll be using VMware Fusion, but the Builder for Fusion will also work with VMware Workstation on Windows and Linux.
  • A text editor to write your Packer files. I prefer VSCode.

Make sure the Builder is installed/available and Packer is installed and working by running packer –version.

Your first Windows Server template

We’re going to create a Windows Server 2016 VM and export the finished VMX and VMDKs to your local hard drive without any customisation.

  1. Create a local working directory to store your work.
  2. Download a Windows Server 2016 trial ISO from Microsoft (https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2016) and save it to your working directory.
  3. While that ISO is downloading, create a Windows unattended installation answer file at http://www.windowsafg.com/server2016.html. You can enter whatever information you like for this part, except for the following:
    1. Username: Make sure the username will be “Administrator”. This is necessary as it’s used by Packer to modify the VM.
    2. Password: Enter packer as the password. I’m using this password just for the tutorial, but you can set it to whatever you want. You’ll need to remember it as it’s used later by Packer files. I expect the password will be reset at a later time when you’re deploying from the template (Guest Customisation Specifications, etc).
  4. Save the unattended installation file to your working directory and name it autounattend.xml.
  5. Create a new JSON file in your working directory and give it a name that relates to the template you’re building. I recommend something short and easy like “WS2016.json”.
  6. Copy and paste the following into your new JSON file and update the highlighted line with the name of your Windows Server ISO:
{
    "builders": [
      {
        "type": "vmware-iso",
        "name": "WIN2016",
        "communicator": "winrm",
        "winrm_username": "Administrator",
        "winrm_password": "packer",
        "winrm_timeout": "4h",
        "disable_vnc": true,
        "iso_url": "Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.ISO",
        "iso_checksum_type": "none",
        "boot_wait": "5s",
        "shutdown_command": "shutdown /s /t 10 /f /d p:4:1 /c \"Packer Shutdown\"",
        "vm_name": "WIN2016",
        "guest_os_type": "windows8srv-64",
        "version": "13",
        "disk_size": 61440,
        "vmdk_name": "WIN2016",
        "vmx_data": {
          "numvcpus": 2,
          "memsize": 2048
        },
        "floppy_files": [
          "autounattend.xml"
        ]
      }
    ]
  }
  1. In your command line/terminal run packer build WS2016.json and watch your template come to life in your Builder.

You’ll see that Packer starts right away, verifying the ISO exists. Packer will also validate the contents of the Packer file to ensure it’s been written correctly. Assuming it has, Fusion/Workstation will pop up and display the new VM being provisioned. By default, Packer will display the VM console and will be visible in Fusion. If you want to change that functionality to hide the console, you’ll need to add “headless”: true to your Packer file under the “Builders” section.

Customising the Windows Server template

OK, we’ve got a Windows Server VM built successfully, but it’s pretty much useless. It’s missing our corporate template customisations (security hardening and performance optimisations, etc). How do we customise the machine automatically with no user input?

Packer has a module called “Provisioners”. Provisioners allow you to specify an action or interaction to perform with the Virtual Machine or with the Guest OS. For example, the Powershell Provisioner is used to connect to the guest OS using WinRM to execute Powershell scripts or commands. At a basic level, the only parameters the Provisioner needs is the path to the local Powershell script in your working directory or the Powershell command, and the WinRM username and password. The Provisioner will handle connection, execution, and output redirection to your local console.

Preparing the PowerShell script

I’ve got a basic one-liner that will clear the event logs on a Windows Server. This is a great script to run at the end of the provisioning process, but for this post I will use it simply to show you how the provisioner works.

Create a new PowerShell script file with the following contents:

wevtutil el | Foreach-Object {wevtutil cl "$_"}

Save the file with the name “clear-event-logs.ps1” and place it in your working directory.

Powershell Provisioner

To use Provisioners, we need to add a new section to our Packer file. Using our basic example above, I’ve added a new Provisioner section (highlighted):

{
    "builders": [
      {
        "type": "vmware-iso",
        "name": "WIN2016",
        "communicator": "winrm",
        "winrm_username": "Administrator",
        "winrm_password": "packer",
        "winrm_timeout": "4h",
        "disable_vnc": true,
        "iso_url": "Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.ISO",
        "iso_checksum_type": "none",
        "boot_wait": "5s",
        "shutdown_command": "shutdown /s /t 10 /f /d p:4:1 /c \"Packer Shutdown\"",
        "vm_name": "WIN2016",
        "guest_os_type": "windows8srv-64",
        "version": "13",
        "disk_size": 61440,
        "vmdk_name": "WIN2016",
        "vmx_data": {
          "numvcpus": 2,
          "memsize": 2048
        },
        "floppy_files": [
          "autounattend.xml"
        ]
      }
    ],
        "provisioners": [
      {
        "type": "powershell",
        "scripts": [
          "clear-event-logs.ps1",
        ]
      }
    ]
  }

The “provisioners” object is actually an array (indicated by the square brackets), with the first and only object in the array being an actual provisioner of “type” PowerShell. Provisioners really only need the “type” and the script or scripts you want to run. You’ll notice I’ve specified an array of scripts to run for this PowerShell Provisioner, but only specified a single script. In this case, a single PowerShell provisioner can run multiple scripts for you under a single WinRM session. By defining an array of only a single object, we can very easily add another script to this Provisioner block (we’ll do that another day).

If you were to run your Packer build now, you’d have a new Windows Server 2016 VM (VMX and VMDK’s on disk). You can now spin it up directly in Workstation or Fusion, or if you really wanted to, copy it to an ESXi datastore. But that sounds a bit painful…

Get the VM to vSphere

We’ve now got a VM template being built from an ISO, based on an autounattend.xml file, with a customisation occurring after deployment. We will now get the VM into our vSphere environment via vCenter. The best part? We’re going to use Packer’s Post-Processor for vSphere.

In your Packer definition file, add the following block after the “provisioners” section:

    "post-processors": [
        {
            "type": "vsphere",
            "cluster": "mycluster",
            "datacenter": "mydatacenter",
            "datastore": "template-datastore",
            "host": "vcenterserver-fqdn",
            "password": "password",
            "username": "username",
            "vm_name": "WIN2019"
        }
    ]

Make sure you update every highlighted field in the block above with your own environments values. What this block is doing is telling Packer to upload the final result of the VM you’ve built to vSphere. These are the minimum requirements but you can check the vSphere Post-Provisioner page for more options.

You should now have a Packer file that looks something like this:

{
    "builders": [
      {
        "type": "vmware-iso",
        "name": "WIN2016",
        "communicator": "winrm",
        "winrm_username": "Administrator",
        "winrm_password": "packer",
        "winrm_timeout": "4h",
        "disable_vnc": true,
        "iso_url": "Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.ISO",
        "iso_checksum_type": "none",
        "boot_wait": "5s",
        "shutdown_command": "shutdown /s /t 10 /f /d p:4:1 /c \"Packer Shutdown\"",
        "vm_name": "WIN2016",
        "guest_os_type": "windows8srv-64",
        "version": "13",
        "disk_size": 61440,
        "vmdk_name": "WIN2016",
        "vmx_data": {
          "numvcpus": 2,
          "memsize": 2048
        },
        "floppy_files": [
          "autounattend.xml"
        ]
      }
    ],
        "provisioners": [
      {
        "type": "powershell",
        "scripts": [
          "clear-event-logs.ps1",
        ]
      }
    ],
        "post-processors": [
        {
            "type": "vsphere",
            "cluster": "mycluster",
            "datacenter": "mydatacenter",
            "datastore": "template-datastore",
            "host": "vcenterserver-fqdn",
            "password": "password",
            "username": "username",
            "vm_name": "WIN2019"
        }
    ]
  }

If you build this template, you’ll get a VM with the event logs cleared, and a completed VM in vSphere.

This post has only touched on what’s possible with Packer and Provisioners. For example, some of my more advanced Packer files include Windows Updates installations, power management settings, security configurations, Windows Explorer tweaks, and so on.

I’ll share these more advanced builds (and more) in my next post. But for now, play around and see what you can build with Packer.

Coming up in Part 2: Advanced Packer templates, and automating the Packer build process with PowerShell.