Introduction to Packer for Windows Server Image Building

HashiCorp Packer is a tool that automates the creation of machine images from a single configuration file. For Windows Server 2019, Packer can build Hyper-V VHDs, VMware VMDK templates, AWS AMIs, Azure managed images, or VirtualBox OVAs—all from the same template definition. Custom Windows Server 2019 images pre-baked with security hardening, required software, and configuration eliminate configuration drift and reduce deployment time from hours to minutes.

Prerequisites


# Install Packer on a Windows Server 2019 build machine
# Download from https://www.packer.io/downloads
# Or use Chocolatey:
choco install packer -y

# Verify installation
packer version
packer plugins installed

# Install required Packer plugins
packer plugins install github.com/hashicorp/vsphere
packer plugins install github.com/hashicorp/hyperv
packer plugins install github.com/hashicorp/azure
packer plugins install github.com/hashicorp/windows-update

Building a Windows Server 2019 Image for Hyper-V

The Packer HCL template defines sources (build environments) and build steps. Below is a complete template for a hardened Windows Server 2019 Server Core image on Hyper-V:


# windows-server-2019.pkr.hcl

packer {
  required_plugins {
    hyperv = {
      version = ">= 1.1.0"
      source  = "github.com/hashicorp/hyperv"
    }
    windows-update = {
      version = ">= 0.15.0"
      source  = "github.com/rgl/windows-update"
    }
  }
}

variable "iso_path" {
  type    = string
  default = "D:\ISOs\WS2019_SERVERDATACENTER.iso"
}

variable "iso_checksum" {
  type    = string
  default = "sha256:the_sha256_checksum_of_your_iso"
}

variable "output_dir" {
  type    = string
  default = "D:\Packer\output\ws2019-core"
}

source "hyperv-iso" "ws2019-core" {
  iso_url              = var.iso_path
  iso_checksum         = var.iso_checksum
  vm_name              = "ws2019-core-template"
  generation           = 2
  cpus                 = 4
  ram_size             = 4096
  disk_size            = 61440
  switch_name          = "vSwitch-Build"
  enable_secure_boot   = true
  secure_boot_template = "MicrosoftWindows"
  headless             = false
  
  shutdown_command     = "shutdown /s /t 10 /f /d p:4:1 /c 'Packer Shutdown'"
  shutdown_timeout     = "30m"
  
  communicator         = "winrm"
  winrm_username       = "Administrator"
  winrm_password       = "Packer!Build2024"
  winrm_use_ssl        = false
  winrm_insecure       = true
  winrm_timeout        = "2h"
  
  # Unattend.xml passed as a virtual floppy for automated installation
  floppy_files         = ["./autounattend.xml", "./SetupComplete.ps1"]
}

build {
  sources = ["source.hyperv-iso.ws2019-core"]

  # Install Windows Updates
  provisioner "windows-update" {
    search_criteria = "IsInstalled=0"
    filters = [
      "include:$_.Title -match 'Security Update'",
      "exclude:$_.Title -match 'Preview'",
    ]
    update_limit    = 50
    restart_timeout = "2h"
  }

  # Run hardening script
  provisioner "powershell" {
    script = "./scripts/Harden-Server.ps1"
  }

  # Run DSC configuration
  provisioner "powershell" {
    inline = [
      "Install-Module PSDesiredStateConfiguration -Force",
      "& C:\Windows\Temp\Apply-DSCBaseline.ps1"
    ]
  }

  # Sysprep for template generalization
  provisioner "windows-restart" { }

  provisioner "powershell" {
    inline = [
      "C:\Windows\System32\Sysprep\sysprep.exe /oobe /generalize /quiet /shutdown /unattend:C:\Windows\Temp\sysprep.xml"
    ]
  }

  post-processor "manifest" {
    output = "${var.output_dir}/packer-manifest.json"
  }
}

Autounattend.xml for Automated Installation


# autounattend.xml (Server Core, Datacenter Edition)
# Place this file on the floppy or at the root of a virtual DVD



  
    
      en-US
      en-US
      en-US
      en-US
      en-US
    
    
      
        
          
            
              1EFI100
            
            
              2MSR16
            
            
              3Primarytrue
            
          
          0true
        
      
      
        
          
            
              /IMAGE/INDEX2
            
          
          03
        
      
      
        true
        Never
      
    
  
  
    
      
        true
        true
        true
        true
        true
        3
      
      
        
          Packer!Build2024true
        
      
      UTC
    
  

Server Hardening Script Invoked by Packer


# scripts/Harden-Server.ps1

$ErrorActionPreference = 'Stop'

# Set TLS 1.2 as default
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Disable SMBv1
Set-SmbServerConfiguration -EnableSMB1Protocol $false -Force

# Disable NetBIOS
Get-WmiObject -Class Win32_NetworkAdapterConfiguration |
    Where-Object { $_.IPEnabled } |
    ForEach-Object { $_.SetTcpipNetbios(2) }

# Enable Windows Firewall on all profiles
Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled True

# Disable Remote Registry
Set-Service -Name RemoteRegistry -StartupType Disabled
Stop-Service -Name RemoteRegistry -Force

# Enable Windows Defender
Set-MpPreference -DisableRealtimeMonitoring $false
Update-MpSignature

# Enable Windows Update automatic download
$wuKey = 'HKLM:SOFTWAREPoliciesMicrosoftWindowsWindowsUpdateAU'
New-Item -Path $wuKey -Force | Out-Null
Set-ItemProperty $wuKey AUOptions 3  # 3 = download only, notify to install

# Configure NTP
w32tm /config /manualpeerlist:"time.windows.com" /syncfromflags:manual /reliable:YES /update
Restart-Service w32tm

# Set screen saver / idle lock
Set-ItemProperty 'HKCU:Control PanelDesktop' -Name 'ScreenSaveTimeOut' -Value 300
Set-ItemProperty 'HKCU:Control PanelDesktop' -Name 'ScreenSaverIsSecure' -Value 1

Write-Output 'Hardening complete'

Building the Image


# Validate the template syntax
packer validate windows-server-2019.pkr.hcl

# Run the build (verbose output)
packer build -on-error=ask windows-server-2019.pkr.hcl

# Build with variable overrides
packer build `
    -var "iso_path=E:ISOsWS2019_Updated.iso" `
    -var "output_dir=D:Imagesws2019-q1" `
    windows-server-2019.pkr.hcl

# Build only a specific source
packer build -only=hyperv-iso.ws2019-core windows-server-2019.pkr.hcl

# After successful build, the VHD is at:
# D:Packeroutputws2019-coreVirtual Hard Disks*.vhdx

Building for Azure


# Add an Azure source to the same template
source "azure-arm" "ws2019-azure" {
  subscription_id = var.azure_subscription_id
  client_id       = var.azure_client_id
  client_secret   = var.azure_client_secret
  tenant_id       = var.azure_tenant_id

  managed_image_name                = "ws2019-core-hardened"
  managed_image_resource_group_name = "rg-packer-images"
  location                          = "East US"

  os_type          = "Windows"
  image_publisher  = "MicrosoftWindowsServer"
  image_offer      = "WindowsServer"
  image_sku        = "2019-datacenter-core"
  image_version    = "latest"

  vm_size          = "Standard_D2s_v3"
  disk_caching_type = "ReadWrite"

  communicator     = "winrm"
  winrm_use_ssl    = true
  winrm_insecure   = true
  winrm_timeout    = "10m"
  winrm_username   = "packer"
}

# Build for Azure
packer build -only=azure-arm.ws2019-azure windows-server-2019.pkr.hcl

Conclusion

Packer automates the creation of Windows Server 2019 images that are pre-hardened, fully patched, and consistently configured before a single VM is ever deployed from them. Using the HCL2 template format, a single configuration file can build images for Hyper-V, VMware, Azure, and AWS simultaneously. Combined with a PowerShell hardening script, Windows Update provisioner, and DSC baseline, Packer images ensure that new servers are born compliant rather than having compliance applied reactively after deployment.