Preparing a Reusable Windows Server Template in PVE
Preparing a Reusable Windows Server Template in PVE
A Windows Server template should do more than reach the desktop. It needs reusable drivers and baseline configuration while allowing every clone to establish its own identity, name, network settings, and management state.
This article combines three KBs: golden-image preparation, QEMU Guest Agent installation, and ballooning. The historical records produced two bootable clones, verified qemu-ga Running and host-side ping for VM101/VM102, and configured a 4096 MiB maximum with a 2048 MiB balloon minimum. Those observations do not prove every later clone inherits and passes all acceptance checks.
Correct the assumptions before copying the template
| Original assertion | Revised interpretation |
|---|---|
| Duplicate SID makes domain joining impossible | Generalize copied deployments; local machine SID and AD computer-object SID are different, and name/account/trust problems need separate investigation |
| Server 2022 requires UEFI | This deployment used q35/OVMF; one 0xc0000225 does not establish an OS-wide firmware requirement |
| Edition conversion always breaks Sysprep | Inspect the failing instance's Panther logs instead of deleting every Appx package |
| FirstLogonCommands wait in Order sequence | Microsoft documents concurrent starts; put dependent steps behind one controlled entry point |
| Delete XML after enabling permanent AutoLogon | Cached answer files and AutoLogon credentials need separate inspection |
Microsoft's deployment requirement to generalize an image is the reason to use Sysprep. Identical hardware may also justify retaining installed devices. That is more precise than claiming all domain-join failures result from duplicate local SIDs. Sysprep generalization
Patch level also matters: Microsoft documents duplicate-SID Kerberos/NTLM failures on Windows 11 24H2/25H2 and Server 2025 following relevant updates from August 29, 2025. Do not invert the old assertion into “duplicate SIDs are harmless.” This Server 2022 case does not establish behavior on those newer systems; generalize through supported deployment methods and accept each clone identity. Microsoft duplicate-SID authentication guidance
Separate baseline configuration from identity
Keep the base VM in WORKGROUP, without promotion to a domain controller. A promoted DC is not an input to this workflow; use the separate DC replacement procedure.
Install the intended VirtIO storage and network drivers before generalization. Verify usable devices and successful boots after restart, rather than relying on an installer exit alone. Archive the ISO source, version, and hash: a moving latest URL is insufficient provenance.
Assign names, static addresses, domain membership, monitoring identifiers, and deployment credentials after cloning. Remove temporary downloads and sessions. Export useful preparation logs before cleanup; deleting errors does not fix them. Validate licensing against the actual channel rather than treating a KMS observation as perpetual activation.
Get-ComputerInfo | Select-Object WindowsProductName,WindowsVersion,OsBuildNumber
Get-CimInstance Win32_ComputerSystem | Select-Object Name,PartOfDomain,Domain
Get-Service qemu-ga -ErrorAction SilentlyContinue
Get-PnpDevice -PresentOnly | Where-Object FriendlyName -match 'VirtIO|QEMU'For this baseline, check PartOfDomain=False and absence of the AD DS role. Missing qemu-ga means integration remains incomplete; it does not mean Windows failed to boot.
Understand the answer-file passes
generalize runs during sealing, specialize handles individualization on the clone, and oobeSystem handles initial setup. Validate settings with Windows System Image Manager against the target image; well-formed XML alone does not establish support.
This deliberately small example shows device retention for identical hardware and a time zone. It contains no passwords, AutoLogon, or complete unattended OOBE configuration.
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="generalize">
<component name="Microsoft-Windows-PnpSysprep"
processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35"
language="neutral" versionScope="nonSxS">
<PersistAllDeviceInstalls>true</PersistAllDeviceInstalls>
</component>
</settings>
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup"
processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35"
language="neutral" versionScope="nonSxS">
<TimeZone>China Standard Time</TimeZone>
</component>
</settings>
</unattend>Configure supported OOBE settings individually. Microsoft explicitly advises against using SkipMachineOOBE to automate OOBE; it is not a harmless fallback. OOBE settings
Do not split dependent installation, activation, service, and cleanup steps into multiple FirstLogonCommands and assume serialized completion. A single entry script with exit-code checks, phase logs, and failure stops is easier to validate. This is a revised design recommendation, not a newly tested Windows deployment. FirstLogonCommands behavior
Generalize, shut down, and clone
Finish updates and pending restarts before sealing. If Sysprep fails, inspect C:\Windows\System32\Sysprep\Panther\setupact.log and setuperr.log and address the specific blocker. Do not run the old blanket Appx deletion procedure or boot the sealed VM merely to inspect its desktop: that starts the next specialization cycle.
C:\Windows\System32\Sysprep\Sysprep.exe /generalize /oobe /shutdown /unattend:C:\Deploy\unattend.xmlOnce sealing succeeds and the VM is stopped, use a test clone. Replace example VMIDs and storage with your own:
qm status 100
qm config 100
qm template 100
qm clone 100 110 --name WS-TEST-01 --full 1 --storage local-lvm
qm config 110A full clone gives an independent disk subject to storage capacity and performance. Start on an isolated network or with the NIC disconnected. Inspect the name, AutoLogon state, and inherited IP before connecting to business networks. Accept one clone before scaling out.
Validate both ends of the Guest Agent
The PVE Agent switch configures the host channel; Windows must also have the service installed and running. VirtIO drivers alone do not establish that. The record used the standalone qemu-ga-x86_64.msi, copied from mounted media to the local disk.
qm set 110 --agent enabled=1
qm pending 110If the channel was added as new hardware, inspect pending changes and perform a complete stop/start when required. In elevated Windows PowerShell:
$install = Start-Process msiexec.exe -Wait -PassThru -ArgumentList '/i','C:\Deploy\qemu-ga-x86_64.msi','/qn','/norestart'
$install.ExitCode
Get-Service qemu-gaExit code 0 normally indicates success; 3010 indicates success requiring restart. Investigate other codes with MSI logs. Then test the channel from PVE:
qm agent 110 ping
qm agent 110 get-osinfo
qm agent 110 get-fsinfo
qm agent 110 network-get-interfacesThese establish communication and readable OS, filesystem, and interface information. Multiple IPv6 addresses are not inherently an error. Backup freezing still requires task-log and restore validation; ping is not a recovery guarantee. PVE Windows guest guidance
Ballooning is a separate integration
Ballooning uses its own VirtIO device and driver. The recorded DCs used memory: 4096 and balloon: 2048; 2 GiB is not a universal server minimum.
qm set 110 --memory 4096 --balloon 2048
qm pending 110
qm status 110The record applied disabled-to-enabled hardware changes with a full shutdown/start. A Windows restart usually does not recreate QEMU devices. After startup:
pvesh get /nodes/pve/qemu/110/status/current --output-format jsonballoon_min=2147483648 corresponds to 2 GiB. Inspect effective configuration and ballooninfo, checking field definitions for the installed version. Guest use, available memory, balloon target, and host QEMU RSS are different measurements. Only a documented pressure test establishes reclaim behavior and business impact; the historical record has no complete pressure curve.
Deliver acceptance evidence with the template
| Layer | Required evidence | Insufficient observation |
|---|---|---|
| Identity | Unique name, intended domain state, applied deployment settings | Desktop appears |
| Drivers | Working storage, network, and balloon devices | ISO was attached |
| Agent | Windows service plus host ping/information queries | agent: 1 alone |
| Memory | No pending changes, runtime information, workload observations | One low RSS reading |
| Credentials | No persistent AutoLogon, inspected caches, rotated deployment password | One XML file deleted |
| Recovery | Test clone/restore boots with correct identity | Backup file exists |
Discard a failed test clone while retaining the base template until a replacement version passes. If the template was contaminated, rebuild from its pre-seal snapshot rather than applying Sysprep indiscriminately to deployed instances. Completion means new instances are individualized and manageable, not that the last script printed success.
The date is the main KB's first Git commit date, 2026-09-15 (UTC+8), commit 6d198b9. Merged sources are retained in metadata; historical operation dates are separate from repository dates. Revised configuration examples were not executed on production devices.
