Packer Overview and Why Use It for Windows Server Images
Packer is an open-source tool from HashiCorp that automates the creation of identical machine images for multiple platforms from a single configuration source. Instead of manually installing and configuring Windows Server 2022, patching it, installing agents, and then capturing it — a process prone to inconsistency — Packer performs all of these steps programmatically and produces a reproducible, versioned image artifact.
The primary benefits for Windows Server 2022 image builds are consistency across environments (Hyper-V, VMware, Azure, AWS all get the same baseline), version-controlled build configuration, automated Windows Update integration, and the ability to layer software provisioning on top of a fully patched base OS before the image is captured.
Packer uses a concept of builders (which platform to create the VM on), provisioners (scripts that run inside the VM to configure it), and post-processors (what to do with the finished image). All of this is defined in HCL2 (HashiCorp Configuration Language) template files.
Installing Packer on Windows Server 2022
Packer is distributed as a single binary. The simplest installation method on Windows Server 2022 is via Chocolatey or by downloading the binary directly from HashiCorp.
# Method 1: Install via Chocolatey (if Chocolatey is already installed)
choco install packer -y
# Method 2: Manual installation
# Download the latest Packer binary
$url = "https://releases.hashicorp.com/packer/1.10.0/packer_1.10.0_windows_amd64.zip"
Invoke-WebRequest -Uri $url -OutFile "C:Temppacker.zip"
Expand-Archive -Path "C:Temppacker.zip" -DestinationPath "C:ToolsPacker"
# Add to system PATH
$currentPath = [System.Environment]::GetEnvironmentVariable("PATH", "Machine")
[System.Environment]::SetEnvironmentVariable("PATH", "$currentPath;C:ToolsPacker", "Machine")
$env:PATH += ";C:ToolsPacker"
# Verify installation
packer version
# Output: Packer v1.10.0
# Initialize plugins for the build (required for HCL2 templates)
# Run from your template directory:
packer init .
Packer HCL2 Template Structure
Modern Packer (v1.7+) uses HCL2 as the primary template format. A Packer project for Windows Server 2022 typically consists of several files in the same directory: the main template file (windows-2022.pkr.hcl), a variables file (variables.pkrvars.hcl), and supporting files like autounattend.xml.
The HCL2 template starts with a packer block declaring required plugins, followed by source blocks defining the builder configuration, and a build block tying everything together:
# windows-2022.pkr.hcl
packer {
required_plugins {
hyperv = {
version = ">= 1.1.3"
source = "github.com/hashicorp/hyperv"
}
vmware = {
version = ">= 1.0.11"
source = "github.com/hashicorp/vmware"
}
azure = {
version = ">= 2.1.3"
source = "github.com/hashicorp/azure"
}
}
}
variable "iso_url" {
type = string
default = "https://software-static.download.prss.microsoft.com/sg/download/888969d5-f34g-4e03-ac9d-1f9786c66749/SERVER_EVAL_x64FRE_en-us.iso"
}
variable "iso_checksum" {
type = string
default = "sha256:3e4fa6d8507b554856fc9ca6079cc402df11a8b79344871669f0251535255325"
}
variable "winrm_username" {
type = string
default = "Administrator"
}
variable "winrm_password" {
type = string
default = "Packer@Build2022!"
sensitive = true
}
Configuring the WinRM Communicator
Unlike Linux Packer builds that use SSH, Windows Server 2022 builds use WinRM (Windows Remote Management) as the communicator. Packer connects to the VM via WinRM to run provisioner scripts after the OS installs. WinRM must be enabled and configured during the unattended install via autounattend.xml.
# In the source block, configure WinRM:
source "hyperv-iso" "windows2022" {
iso_url = var.iso_url
iso_checksum = var.iso_checksum
# VM settings
vm_name = "WS2022-Base"
cpus = 4
memory = 4096
disk_size = 61440 # 60 GB in MB
generation = 2
enable_secure_boot = false # Disable for unattended install compatibility
switch_name = "Default Switch"
# Boot command to trigger autounattend
boot_command = [""]
boot_wait = "5s"
# WinRM communicator settings
communicator = "winrm"
winrm_username = var.winrm_username
winrm_password = var.winrm_password
winrm_use_ssl = false
winrm_insecure = true
winrm_timeout = "4h" # Long timeout to account for Windows Update
# Point to autounattend.xml via virtual floppy
floppy_files = [
"autounattend.xml",
"scripts/enable-winrm.ps1",
"scripts/set-temp-password.ps1"
]
shutdown_command = "shutdown /s /t 10 /f /d p:4:1 /c "Packer Shutdown""
shutdown_timeout = "30m"
}
Packer autounattend.xml for Unattended Windows Server 2022 Install
The autounattend.xml file is the Windows Answer File that automates the Windows installation. It is placed on a virtual floppy disk that Packer mounts to the VM. This file handles disk partitioning, product key entry, locale settings, administrator password configuration, and — critically — enabling WinRM so Packer can connect.
en-US
en-US
en-US
en-US
en-US
1
EFI
500
2
MSR
128
3
Primary
true
1
1
FAT32
2
3
NTFS
C
0
true
0
3
false
WX4NM-KYWYW-QJJR4-XV3QB-6VM33
Never
true
Packer
MyOrg
PACKER-BUILD
Packer@Build2022!
true
true
Administrator
5
1
cmd /c "a:enable-winrm.ps1"
Enable WinRM for Packer
false
true
true
true
true
true
1
true
true
Packer@Build2022!
true
The enable-winrm.ps1 script referenced in FirstLogonCommands must configure WinRM to accept Packer connections:
# scripts/enable-winrm.ps1
Write-Output "Configuring WinRM for Packer..."
# Enable WinRM
Enable-PSRemoting -Force -SkipNetworkProfileCheck
# Allow unencrypted (for local network builds; use HTTPS for production)
Set-Item WSMan:localhostServiceAllowUnencrypted -Value True
Set-Item WSMan:localhostServiceAuthBasic -Value True
# Configure WinRM listener
winrm quickconfig -q
winrm set winrm/config '@{MaxTimeoutms="7200000"}'
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="2048"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
# Open firewall for WinRM
netsh advfirewall firewall add rule name="WinRM (HTTP-In)" `
dir=in action=allow protocol=TCP localport=5985
Write-Output "WinRM configured successfully."
Provisioners: PowerShell and Windows Shell
After Packer connects via WinRM, provisioners run sequentially to configure the image. The build block in your HCL2 template defines the provisioners:
build {
name = "windows-2022-base"
sources = ["source.hyperv-iso.windows2022"]
# Run an inline PowerShell script
provisioner "powershell" {
inline = [
"Set-ExecutionPolicy Bypass -Scope Process -Force",
"Install-WindowsFeature -Name NET-Framework-45-Core",
"Set-TimeZone -Id 'UTC'"
]
}
# Run a local PowerShell script file
provisioner "powershell" {
script = "scripts/install-updates.ps1"
execution_policy = "bypass"
elevated_user = "Administrator"
elevated_password = var.winrm_password
}
# Windows shell (cmd.exe) provisioner
provisioner "windows-shell" {
inline = [
"sc config wuauserv start= auto",
"net start wuauserv"
]
}
# Install Chocolatey and common tools
provisioner "powershell" {
script = "scripts/install-tools.ps1"
}
# Run sysprep last, immediately before capture
provisioner "powershell" {
script = "scripts/sysprep.ps1"
}
}
Installing Windows Updates in Packer
One of the most time-consuming but important parts of a gold image build is applying all available Windows updates. The install-updates.ps1 script uses the PSWindowsUpdate module to install patches non-interactively:
# scripts/install-updates.ps1
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Install NuGet provider and PSWindowsUpdate module
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Install-Module -Name PSWindowsUpdate -Force -Confirm:$false
# Import and run update installation
Import-Module PSWindowsUpdate
Write-Output "Installing Windows Updates..."
Install-WindowsUpdate -AcceptAll -AutoReboot:$false -Verbose
# Log installed updates
$installedUpdates = Get-WUHistory | Select-Object -First 20
$installedUpdates | Format-Table Title, Date, Result
Write-Output "Updates complete. Rebooting..."
Restart-Computer -Force
Because Windows Update may require multiple reboots, the Packer provisioner can use the restart_check_command and restart_command options to handle reboots gracefully:
provisioner "windows-restart" {
restart_check_command = "powershell -command "& {if ((Get-Content 'C:\packer_restart_needed.txt' -ErrorAction SilentlyContinue) -eq 'reboot_needed') { exit 1 } else { exit 0 }}""
restart_timeout = "30m"
}
Sysprep in Packer
Before Packer captures the image, sysprep must run to generalize it — removing machine-specific information like SIDs, computer names, and driver settings. The sysprep script runs as the final provisioner:
# scripts/sysprep.ps1
Write-Output "Starting Sysprep generalization..."
# Clean up Packer artifacts before sysprepping
Remove-Item -Path "C:WindowsTemp*" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path "C:Temp*" -Recurse -Force -ErrorAction SilentlyContinue
# Run Sysprep in OOBE generalize mode
& "C:WindowsSystem32Sysprepsysprep.exe" /oobe /generalize /quiet /quit
# Wait for sysprep to complete
do {
Start-Sleep -Seconds 5
$sysprepStatus = (Get-Item "HKLM:SOFTWAREMicrosoftWindows NTCurrentVersionSoftwareProtectionPlatform").GetValue("")
} while ((Get-Process -Name sysprep -ErrorAction SilentlyContinue))
Write-Output "Sysprep complete. Image is ready for capture."
Building for Hyper-V, VMware, and Azure
The same provisioner configuration can be reused across multiple builders by adding multiple source blocks. For VMware vSphere or ESXi:
source "vmware-iso" "windows2022" {
iso_url = var.iso_url
iso_checksum = var.iso_checksum
vm_name = "WS2022-Template"
cpus = 4
memory = 4096
disk_size = 61440
disk_type_id = 1 # thin provision
guest_os_type = "windows2019srvnext-64"
vmx_data = {
"virtualHW.version" = "19"
"scsi0.virtualDev" = "pvscsi"
"ethernet0.virtualDev" = "vmxnet3"
}
communicator = "winrm"
winrm_username = var.winrm_username
winrm_password = var.winrm_password
winrm_timeout = "4h"
floppy_files = ["autounattend.xml", "scripts/enable-winrm.ps1"]
shutdown_command = "shutdown /s /t 10 /f"
}
For Azure using the azure-arm builder, you build from an existing Azure Marketplace image rather than an ISO, so there is no autounattend.xml needed:
source "azure-arm" "windows2022" {
client_id = var.azure_client_id
client_secret = var.azure_client_secret
tenant_id = var.azure_tenant_id
subscription_id = var.azure_subscription_id
managed_image_resource_group_name = "rg-packer-images"
managed_image_name = "WS2022-Custom-{{timestamp}}"
managed_image_storage_account_type = "Standard_LRS"
os_type = "Windows"
image_publisher = "MicrosoftWindowsServer"
image_offer = "WindowsServer"
image_sku = "2022-datacenter-azure-edition"
image_version = "latest"
location = "East US"
vm_size = "Standard_D4s_v3"
communicator = "winrm"
winrm_use_ssl = true
winrm_insecure = true
winrm_timeout = "10m"
winrm_username = "packer"
# Azure handles WinRM bootstrap automatically via Azure VM Extension
os_disk_size_gb = 128
azure_tags = {
Environment = "Golden-Image"
CreatedBy = "Packer"
}
}
Post-Processors: Vagrant Box, Manifest, and Compress
Post-processors transform the built artifact after capture. Common post-processors for Windows Server 2022 images include vagrant (creates a .box file for Vagrant), manifest (records build metadata to a JSON file), and compress (compresses the output):
build {
sources = ["source.hyperv-iso.windows2022"]
# ... provisioners ...
post-processor "vagrant" {
output = "builds/WS2022-{{.Provider}}-{{timestamp}}.box"
keep_input_artifact = true
}
post-processor "manifest" {
output = "builds/manifest.json"
strip_path = true
custom_data = {
build_version = "1.0.0"
build_date = timestamp()
os_version = "Windows Server 2022"
}
}
}
To run the build for a specific builder:
# Initialize required plugins
packer init windows-2022.pkr.hcl
# Validate the template
packer validate windows-2022.pkr.hcl
# Build only the Hyper-V image
packer build -only="hyperv-iso.windows2022" windows-2022.pkr.hcl
# Build all defined images
packer build windows-2022.pkr.hcl
# Build with variable overrides
packer build -var "winrm_password=MySecurePass!" windows-2022.pkr.hcl
# Enable debug output
PACKER_LOG=1 packer build windows-2022.pkr.hcl
Packer transforms Windows Server 2022 image creation from a manual, error-prone process into a version-controlled, automated pipeline. Once the initial template and scripts are established, rebuilding with updated patches or software is a single command that produces identical, production-ready images across every target platform.