Proxmox

Windows Server 2025 Proxmox Packer Build Fully Automated

Learn how to automate Windows Server 2025 Proxmox Packer build that is fully automated with this step-by-step guide.

Since updating my VMware vSphere Packer build template for Windows Server 2025 (you can read that here: Windows Server 2025 Packer Build for VMware vSphere), I decided to tackle Proxmox, which wasn’t as straightforward. I wanted to share with you what I have come up with for automated installations of Windows Server 2025 in Proxmox VE Server. There are a few moving parts, but this is the first stab at getting some automation with Server 2025 into my Proxmox environment.

Overview of steps

As the old saying goes, “there is more than one way to skin a cat”. So keep that in mind as there may be a better way to do some of this and I generally start out with automation a bit rough around the edges and then you find better ways to do things. But, in my home lab environment, this definitely got me up and running with an automated Packer build for Windows Server 2025 running on Proxmox.

  1. Create an API token
  2. Download ISOs and Tools
  3. Modify your Windows Server 2025 ISO to include additional files
  4. Create your Packer files
  5. Run packer init and packer build

1. Create an API token

The first step is to create an API token in Proxmox. This is so Packer can authenticate and interact with the Proxmox API endpoint. You can find your tokens in Datacenter >

Create the token in proxmox ve server
Create the token in proxmox ve server
Copy and record your proxmox api token
Copy and record your proxmox api token

You will only be shown the API token once, so we need to copy this value and paste it somewhere to use in our sensitve-variables.pkr.hcl file which I will show below.

2. Download ISOs and Tools

There are two ISOs that we need to download:

First, I downloaded the latest Windows Server 2025 ISO. You can get that from the Microosft Evaluations Center if you want. After you get your ISO, you will want to have the Windows Assessment and Deployment Kit downloaded and installed so you have access to the WADK Deployment Tools, including the Command Prompt: Deployment and Imaging Tools Environment. This will make creating a custom ISO extremely easy and painless.

3. Modify your Windows Server 2025 ISO to include additional files

This is the step that I am not too thrilled about since I would rather not have to mess with the media and just download the ISO and be done with that part. However, with the proxmox-iso packer module, I just kept running into limitation after limitation with getting the files where I needed them. So, this is the “sledgehammer” approach to solve the problem.

Mount the downloaded Windows Server 2025 ISO in Windows or another OS where you can literally copy the files to a folder. I am using Windows, so I mounted the ISO in Windows Explorer and copied the files to a folder on my D:\ drive which has a lot of storage.

Mounted windows server 2025 iso image
Mounted windows server 2025 iso image

Now, there are 3 mods we need to make to this folder that contains the “copied” Windows Server 2025 files:

  • Add the autounattend.xml file
  • Add the setup.ps1 file
  • Add the VirtIO drivers to a $WinpeDriver$ folder in the root of the ISO

autounattend.xml

Ok let’s look at the autounattend.xml file. Below is the generic file that I am using. I have bolded a few places to make it a bit easier to pick out. You need to change the passwords to what you want them to be and also the d:\setup.ps1 is the drive letter of the additional ISO that I mounted in my environment. If yours is different using my packer build file, you will need to adjust accordingly. Also, the product key is the documented Microsoft KMS key for Windows Server 2025 standard.

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="windowsPE">
        <component name="Microsoft-Windows-International-Core-WinPE" 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">
            <SetupUILanguage>
                <UILanguage>en-US</UILanguage>
            </SetupUILanguage>
			<InputLocale>en-US</InputLocale>
            <SystemLocale>en-US</SystemLocale>
            <UILanguage>en-US</UILanguage>
            <UserLocale>en-US</UserLocale>
        </component>
        <component name="Microsoft-Windows-Setup" 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">
            <DiskConfiguration>
                <Disk wcm:action="add">
                    <CreatePartitions>
                        <CreatePartition wcm:action="add">
                            <Type>EFI</Type>
                            <Size>512</Size>
                            <Order>1</Order>
                        </CreatePartition>
                        <CreatePartition wcm:action="add">
                            <Extend>false</Extend>
                            <Type>MSR</Type>
                            <Order>2</Order>
                            <Size>128</Size>
                        </CreatePartition>
                        <CreatePartition wcm:action="add">
                            <Order>3</Order>
                            <Type>Primary</Type>
                            <Extend>true</Extend>
                        </CreatePartition>
                    </CreatePartitions>
                    <ModifyPartitions>
                        <ModifyPartition wcm:action="add">
                            <Format>FAT32</Format>
                            <Order>1</Order>
                            <PartitionID>1</PartitionID>
                        </ModifyPartition>
                        <ModifyPartition wcm:action="add">
                            <Order>2</Order>
                            <PartitionID>2</PartitionID>
                        </ModifyPartition>
                        <ModifyPartition wcm:action="add">
                            <Format>NTFS</Format>
                            <Label>Windows</Label>
                            <Order>3</Order>
                            <PartitionID>3</PartitionID>
                        </ModifyPartition>
                    </ModifyPartitions>
                    <DiskID>0</DiskID>
                    <WillWipeDisk>true</WillWipeDisk>
                </Disk>
            </DiskConfiguration>
            <ImageInstall>
                <OSImage>
                    <InstallFrom>
                        <MetaData wcm:action="add">
                            <Key>/IMAGE/NAME</Key>
                            <Value>Windows Server 2025 Standard (Desktop Experience)</Value>
                        </MetaData>
                    </InstallFrom>
                    <InstallTo>
                        <DiskID>0</DiskID>
                        <PartitionID>3</PartitionID>
                    </InstallTo>
                    <WillShowUI>OnError</WillShowUI>
                    <InstallToAvailablePartition>false</InstallToAvailablePartition>
                </OSImage>
            </ImageInstall>
            <UserData>
			    <AcceptEula>true</AcceptEula>
                <ProductKey>
                    <WillShowUI>Never</WillShowUI>
                    <Key>TVRH6-WHNXV-R9WG3-9XRFY-MY832</Key>
                </ProductKey>
            </UserData>
        </component>
    </settings>
    <settings pass="specialize">
        <component name="Microsoft-Windows-Shell-Setup" 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">
            <TimeZone>Central Standard Time</TimeZone>
        </component>
        <component name="Microsoft-Windows-Deployment" 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">
            <RunSynchronous>
                <RunSynchronousCommand wcm:action="add">
                    <Description>disable product key request</Description>
                    <Order>1</Order>
                    <Path>reg add &quot;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\OOBE&quot; /v SetupDisplayedProductKey /t REG_DWORD /d 1 /f</Path>
                </RunSynchronousCommand>
            </RunSynchronous>
        </component>
		<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" 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">
            <fDenyTSConnections>false</fDenyTSConnections>
        </component>
        <component name="Networking-MPSSVC-Svc" 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">
            <FirewallGroups>
                <FirewallGroup wcm:action="add" wcm:keyValue="RemoteDesktop">
                    <Active>true</Active>
                    <Group>Remote Desktop</Group>
                    <Profile>all</Profile>
                </FirewallGroup>
            </FirewallGroups>
        </component>
        <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" 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">
            <SecurityLayer>2</SecurityLayer>
            <UserAuthentication>1</UserAuthentication>
        </component>
		<component name="Microsoft-Windows-ServerManager-SvrMgrNc" 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">
            <DoNotOpenServerManagerAtLogon>true</DoNotOpenServerManagerAtLogon>
        </component>
    </settings>
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-Shell-Setup" 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">
            <AutoLogon>
                <Password>
                    <Value>password</Value>
                    <PlainText>true</PlainText>
                </Password>
                <LogonCount>2</LogonCount>
                <Username>Administrator</Username>
                <Enabled>true</Enabled>
            </AutoLogon>
            <FirstLogonCommands>
                <SynchronousCommand wcm:action="add">
                    <Order>1</Order>
                    <CommandLine>powershell -ExecutionPolicy Bypass -File d:\setup.ps1</CommandLine>
                    <Description>Enable WinRM service</Description>
                    <RequiresUserInput>true</RequiresUserInput>
                </SynchronousCommand>
            </FirstLogonCommands>
            <UserAccounts>
                <AdministratorPassword>
                    <Value>password</Value>
                    <PlainText>true</PlainText>
                </AdministratorPassword>
            </UserAccounts>
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
                <HideLocalAccountScreen>true</HideLocalAccountScreen>
                <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
                <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
            </OOBE>
        </component>
    </settings>
    <cpi:offlineImage cpi:source="wim:c:/wims/install.wim#Windows Server 2025 SERVERDATACENTER" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend>

setup.ps1

Now, the setup.ps1 file which is used to be called after Windows is installed to do things like install VirtIO tools, etc. Below, I am assuming you are mounting the VirtIO ISO as an additionnal ISO image to your Proxmox VM getting built. For me this was mounted to the E drive.

$ErrorActionPreference = "Stop"

# Switch network connection to private mode
$profile = Get-NetConnectionProfile
Set-NetConnectionProfile -Name $profile.Name -NetworkCategory Private

# Create a folder for installation logs
$logFolder = 'C:\install_logs'
if (-not (Test-Path -Path $logFolder)) {
    Write-Host "Creating log folder at $logFolder..."
    New-Item -Path $logFolder -ItemType Directory -Force | Out-Null
}

# Mount VirtIO ISO and install drivers silently
$virtioDrive = "E:"  # Assuming the VirtIO ISO is mounted as drive E:
$virtioInstaller = Join-Path -Path $virtioDrive -ChildPath "virtio-win-gt-x64.msi"
$qemuInstaller = Join-Path -Path $virtioDrive -ChildPath "guest-agent\qemu-ga-x64.msi"

# Install VirtIO drivers
if (Test-Path $virtioInstaller) {
    Write-Host "Running VirtIO driver installation from $virtioInstaller..."
    try {
        # Execute the silent installation
        Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$virtioInstaller`" /qn ADDLOCAL=ALL /norestart" -Wait -NoNewWindow
        Write-Host "VirtIO driver installation completed successfully."
    }
    catch {
        Write-Error "Failed to run VirtIO driver installation: $_"
        exit 1
    }
}
else {
    Write-Error "VirtIO installer not found at $virtioInstaller. Exiting..."
    exit 1
}

# Install QEMU Guest Agent
if (Test-Path $qemuInstaller) {
    Write-Host "Installing QEMU Guest Agent from $qemuInstaller..."
    try {
        Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$qemuInstaller`" /qn" -Wait -NoNewWindow
        Start-Service -Name qemu-ga
        Set-Service -Name qemu-ga -StartupType Automatic
        Write-Host "QEMU Guest Agent installed and configured successfully."
    }
    catch {
        Write-Error "Failed to install QEMU Guest Agent: $_"
        exit 1
    }
}
else {
    Write-Error "QEMU Guest Agent installer not found at $qemuInstaller. Skipping installation."
}

# Install PS Windows Update Module
Write-Host "Installing PSWindowsUpdate module..."
Get-PackageProvider -Name nuget -Force | Out-Null
Install-Module PSWindowsUpdate -Confirm:$false -Force

# Install Windows updates without user interaction and suppress reboot prompts
Write-Host "Running Windows Update..."
Get-WindowsUpdate -MicrosoftUpdate -Install -IgnoreUserInput -AcceptAll -IgnoreReboot | Out-File -FilePath "$logFolder\windowsupdate.log" -Append

# WinRM Configuration
Write-Host "Configuring WinRM..."
winrm quickconfig -quiet
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service/auth '@{Basic="true"}'

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

# Trigger a single reboot at the end
Write-Host "Rebooting the system..."
Restart-Computer -Force

$WinpeDriver$ folder

I created a $WinpeDriver$ folder to house the VirtIO drivers needed when Windows Server 2025 boots into the PE setup. You know how when you manually install Windows Server in Proxmox, it will not find the hard drive. You have to mount the VirtIO drivers and point the installer at the drivers. By adding this folder to the root of our ISO for Windows we are taking care of that problem. What files does it contain? Note below.

Files contained in the drivers folder on the custom iso
Files contained in the drivers folder on the custom iso

Where did I get these files? You mount the VirtIO ISO that you download from Oracle here: Windows VirtIO Drivers.

Navigate in that mounted ISO to the folder structure you see in the screenshot above: vioscsi\2k25\amd64 and you will find the files there. Just copy the who directory structure to the $WinpeDriver$ folder.

Ok so after creating the files above and downloading what we need, your folder where you copied everything into from the ISO image should look like this with the underlined, being the custom files I have added.

Custom files added to the windows server 2025 iso
Custom files added to the windows server 2025 iso

Now, how do we turn this back into an ISO? Now we can use the Command Prompt: Deployment and Imaging Tools Environment shell which gets installed when you install the WADK. Use the command below, replacing paths as needed. D:\win2025 is my custom folder where I copied everything:

oscdimg -m -o -u2 -bootdata:2#p0,e,bD:\win2025\boot\etfsboot.com#pEF,e,bD:\win2025\efi\microsoft\boot\efisys.bin D:\win2025 D:\win2025_unattend-noupdates.iso

The output is our new ISO image file: D:\win2025_unattend.iso.

Running the command to build the custom windows server 2025 iso image
Running the command to build the custom windows server 2025 iso image

Upload this file to your Proxmox VE Server ISO storage:

Upload your custom windows server 2025 iso to proxmox ve server iso storage
Upload your custom windows server 2025 iso to proxmox ve server iso storage

4. Create your Packer files

For Packer, I created 3 files:

  • sensitive-variables.pkr.hcl
  • variables.pkr.hcl
  • windows-server-2025.pkr.hcl

The sensitive-variables.pkr.hcl file looks like this:

# Proxmox API Sensitive Variables
variable "proxmox_api_url" {
  type    = string
  default = "https://10.3.33.52:8006/api2/json" ##your proxmox server address
}

variable "proxmox_api_token_id" {
  type    = string
  default = "root@pam!packer" ##needs to match your token description
}

variable "proxmox_api_token_secret" {
  type      = string
  default   = "ed419d55-bb1a-4b31-a188-4599888fae7d" ##your token that you are creating 
  sensitive = true
}

# Windows-specific sensitive variable
variable "admin_password" {
  type      = string
  default   = "password" ##replace with your admin password for winrm
  sensitive = true
}

Next, the variables.pkr.hcl file:

# Windows Server-specific non-sensitive variables

variable "vm_name" {
  type    = string
  default = "windows-server-2025"
}

variable "disk_size" {
  type    = number
  default = 61440
}

variable "memory" {
  type    = number
  default = 4096
}

variable "cpus" {
  type    = number
  default = 2
}

variable "http_server_ip" {
  type    = string
  default = "10.3.33.224"  # Replace with your actual IP
}

variable "http_server_port" {
  type    = number
  default = 8000             # Replace with your desired port
}

Finally, the windows-server-2025.pkr.hcl file:

# Windows Server 2025 Packer Template for Proxmox

packer {
  required_plugins {
    proxmox = {
      version = "~> 1"
      source  = "github.com/hashicorp/proxmox"
    }
  }
}

source "proxmox-iso" "windows2025" {

  # Proxmox Host Connection
  proxmox_url              = var.proxmox_api_url
  insecure_skip_tls_verify = true
  username                 = var.proxmox_api_token_id
  token                    = var.proxmox_api_token_secret
  node                     = "pvetest" # Replace with your Proxmox node's actual hostname

  # BIOS - UEFI
  bios    = "ovmf"
  machine = "q35"

  efi_config {
    efi_storage_pool  = "local-lvm"
    pre_enrolled_keys = true
  }

  # Windows Server ISO File
  iso_file    = "local:iso/win2025_unattend-noupdates.iso"
  unmount_iso = true

  additional_iso_files {

    device       = "ide3"                             # Mount as another CD-ROM
    iso_file     = "local:iso/virtio-win-0.1.262.iso" # Path to VirtIO ISO
    unmount      = true                               # Automatically unmount after build
    iso_checksum = "your_iso_checksum_here"
  }

  # VM General Settings
  vm_name              = "win2025-template"
  template_name        = "win2025-template"
  template_description = "Windows Server 2025 Template"
  memory               = 4096 # Adjust memory as needed
  cores                = 2    # Adjust cores as needed
  cpu_type             = "host"
  os                   = "win11"
  scsi_controller      = "virtio-scsi-pci"
  qemu_agent           = true

  # Network Configuration
  network_adapters {
    model  = "virtio"
    bridge = "vmbr0" # Replace with your actual bridge name
  }

  # Disk Configuration
  disks {
    storage_pool = "local-lvm"
    type         = "scsi"
    disk_size    = "40G"
    cache_mode   = "writeback"
    format       = "raw"
  }

  # WinRM Configuration
  communicator   = "winrm"
  winrm_username = "Administrator"
  winrm_password = var.admin_password
  winrm_timeout  = "12h"
  winrm_use_ssl  = false    # Ensure no SSL if using unsecured connections
  winrm_insecure = true     # Allow insecure connections

  # Boot Settings
  boot_wait = "3s"
  boot_command = [
    "<spacebar><spacebar>" # Simulate pressing "any key" to boot from CD-ROM
  ]
}

build {
  name    = "Proxmox Windows Server 2025 Build"
  sources = ["source.proxmox-iso.windows2025"]

}

5. Run packer init and packer build

Finally, after we have everything in place, we run the following from within the directory where your hcl files are located:

packer init .
packer build .
Running the packer build for windows server 2025
Running the packer build for windows server 2025
Windows server 2025 automated installation running in proxmox
Windows server 2025 automated installation running in proxmox
The setup.ps1 script running in the first run command stage of the proxmox packer build of windows server 2025
The setup.ps1 script running in the first run command stage of the proxmox packer build of windows server 2025
Windows server 2025 template in proxmox
Windows server 2025 template in proxmox

Wrapping up

This project will probably morph as I find new and better ways to do things here. I don’t like modifying ISOs, but quite frankly the automation tools for Proxmox are just not as good as VMware as of yet. I find I need to do hacky little things to get things working smoothly, but that is the nature of the beast. Proxmox is maturing rapidly and as more adopt Proxmox VE these processes will get easier. Anyways enjoy fully automated Windows Server 2025 with Packer in Proxmxo VE Server!

Subscribe to VirtualizationHowto via Email ๐Ÿ””

Enter your email address to subscribe to this blog and receive notifications of new posts by email.



Brandon Lee

Brandon Lee is the Senior Writer, Engineer and owner at Virtualizationhowto.com, and a 7-time VMware vExpert, with over two decades of experience in Information Technology. Having worked for numerous Fortune 500 companies as well as in various industries, He has extensive experience in various IT segments and is a strong advocate for open source technologies. Brandon holds many industry certifications, loves the outdoors and spending time with family. Also, he goes through the effort of testing and troubleshooting issues, so you don't have to.

Related Articles

Leave a Reply

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

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