Skip to main content
RunBook Academy

Proxmox VEXXVI · Windows GuestsFleets of Windows guests

Windows templates and sysprep

Advanced⏱ ~35 minqmsysprepcloudbase-init

What you'll learn

  • State what sysprep /generalize removes and why Microsoft does not support duplication without it
  • Build a Proxmox Windows template using audit mode, an answer file and qm template
  • Choose between /mode:vm and a full generalize, and know the constraint /mode:vm imposes
  • Explain the failures a non-generalised clone produces and how long they stay hidden
  • Configure cloudbase-init against the Proxmox Cloud-Init drive, including the citype default that ostype selects

Prerequisites

Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12

Not yet marked complete on this device.

Cloning a Linux guest is a rename, a new SSH host key, a new machine-id and a new IP. Cloud-Init does all four before the first login prompt appears, and if you forget, you get a duplicate hostname and an SSH warning, both of which announce themselves immediately.

Cloning a Windows guest without generalising it produces machines that are externally distinct — different names, different addresses — and internally identical. Nothing warns you. The estate works. Then, somewhere between three weeks and two years later, patching stops reporting, or activation collapses, or a domain join starts failing for reasons that make no sense, and the common cause is a template built in an afternoon by someone who cloned a working server.

What Microsoft actually says

This is not a best practice. It is a support boundary, and the wording in the sysprep command-line reference is unusually direct:

Moving or copying a Windows image to a different computer without running the Sysprep /generalize command is not supported. You must use the Sysprep /generalize command to generalize a complete Windows installation before you can use the installation for deployment to a new computer, whether you use imaging, hard disk duplication, or another method.

“Hard disk duplication, or another method” includes qm clone. A Proxmox full clone or linked clone is hard disk duplication with a new VMID attached.

And the reference is equally clear that identical hardware does not exempt you:

If you intend to create an image of an installation for deployment to a different computer, you must run the Sysprep command together with the /generalize option, even if the other computer has the same hardware configuration.

That sentence exists because the objection it answers is so natural. Every clone of a Proxmox template has exactly the same virtual hardware. It makes no difference: the things sysprep removes are not hardware-derived.

What /generalize removes

Three items, given verbatim:

Prepares the Windows installation to be imaged. Sysprep removes all unique system information from the Windows installation. Sysprep resets the security ID (SID), clears any system restore points, and deletes event logs.

The next time that the computer starts, the specialize configuration pass runs. The configuration pass creates a new security ID (SID).

The SID is the one that matters, and the reason it matters is not the SID itself. It is everything derived from it.

Derived from the machine identityWhat a duplicate looks likeHow long it stays hidden
Machine SIDLocal accounts and groups on every clone share RIDsIndefinitely, until something enumerates across machines
Computer nameA name conflict on the network, or a rename that leaves stale referencesDays
WSUS/Update client identifierClones collapse into one entry in the update server; patch compliance is fictionWeeks — until an audit
KMS client machine identifierThe KMS host counts many machines as one, and the activation count never reaches its thresholdWeeks to months
Remote Desktop self-signed certificateEvery clone presents the same RDP certificateUntil somebody looks
Domain machine account materialRejoin and trust failures that read as network faultsVariable, and always at the worst time

The middle two are the ones that cost money and reputation. Patch compliance reporting that silently covers a fraction of the estate is worse than no reporting, because it is believed. And a KMS host that never reaches its activation threshold produces an estate of machines in grace periods, all expiring around the same time.

Building the template

The workflow has five phases. Phase four is the one that is easy to get wrong.

1. Build the base

Install Windows following Lesson 1 — win11 or win10 ostype, q35, OVMF, virtio-scsi-single, and the storage driver loaded during setup. Install the VirtIO bundle, the guest agent and the balloon service from Lesson 2. Patch fully. Do not join the domain. A template should never carry a machine account.

2. Customise in audit mode

Audit mode exists exactly for this. It bypasses OOBE so you reach a desktop without creating a user account, and you can install applications and drivers there:

Audit mode enables you to add customizations to Windows images. Audit mode does not require that you apply settings in OOBE. By bypassing OOBE, you can access the desktop more quickly and perform your customizations.

Enter it with Sysprep /audit /reboot, or configure it in an answer file with the reseal mode setting. Everything you install in audit mode survives generalisation.

3. Write the answer file

An unattend.xml is what turns a template into unattended provisioning. It is processed in configuration passes; the two you care about for a Proxmox template are:

  • specialize — runs on first boot after generalisation, on the new machine. Computer name, domain join, product key. The reference notes that to activate automatically you specify a valid product key in the Microsoft-Windows-Shell-Setup ProductKey setting during this pass.
  • oobeSystem — runs immediately before OOBE. Locale, time zone, administrator password, skipping the OOBE screens.

Build it with Windows System Image Manager against your exact install media, not by editing a file from a blog post — the schema is version-specific and a setting that does not apply to your image is silently ignored, which is the worst possible failure mode for an answer file.

4. Generalise

Destructivegeneralise the image, from an elevated prompt inside the guest
# Full generalize, using an answer file, then shut down
C:\Windows\System32\Sysprep\Sysprep.exe /generalize /oobe /shutdown /quiet /unattend:C:\Windows\System32\Sysprep\unattend.xml

/quiet is not optional on Server Core. The reference is emphatic: you must run sysprep from the command line and specify /quiet on a Server Core installation, “Otherwise, the Sysprep UI won’t show, and the process will fail silently.”

There is a second mode worth knowing about, and a constraint that comes with it:

/mode:vm — Generalizes a Virtual Hard Disk (VHD) so that you can deploy the VHD as a VHD on the same Virtual Machine (VM) or hypervisor. After the VM restarts, the VM can boot to OOBE.

The only additional switches that apply to VM mode are /reboot, /shutdown, and /quit. You must deploy the VHD on a Virtual Machine (VM) or hypervisor with the same hardware profile. […] Deploying the VHD to a different VM with a different hardware profile might cause unexpected issues.

You can only run VM mode from inside a VM.

/mode:vm skips hardware re-detection, which makes first boot considerably faster. The price is the hardware-profile constraint, and for a Proxmox estate that constraint has teeth: the machine version pinned at creation (Lesson 1) is part of the hardware profile, and so is the firmware and the disk controller. A template generalised with /mode:vm on a pc-q35-10.0 OVMF guest is a template for pc-q35-10.0 OVMF guests.

Use /mode:vm when your estate is genuinely homogeneous and you clone from the template onto identical configuration. Use a plain /generalize when the template has to serve several profiles, or when you might move it to another hypervisor. If you are not sure, use the plain form; the cost is one slower first boot per clone.

5. Turn it into a Proxmox template

Configuration changeconvert the generalised VM into a template
VMID=9100

# The guest must be stopped, and stopped because sysprep shut it down
qm status "$VMID"

# Remove the install ISO and the VirtIO ISO from the template
qm set "$VMID" --delete ide2
qm set "$VMID" --ide0 none,media=cdrom

# Add a Cloud-Init drive; configdrive2 is the default for a Windows ostype
qm set "$VMID" --ide1 local-lvm:cloudinit

qm template "$VMID"

Cloud-Init on Windows: cloudbase-init

Part IX covered Cloud-Init for Linux templates. Windows has a reimplementation, and Proxmox supports it directly. From the Cloud-Init support documentation:

The default depends on the configured operating system type (ostype). We use the nocloud format for Linux, and configdrive2 for windows.

So citype has three allowed values — configdrive2, nocloud and opennebula — and the correct one for Windows is selected automatically by the ostype you set in Lesson 1. This is the second time that field has paid for itself: a Windows guest left at ostype: other gets a nocloud drive that cloudbase-init, configured with its default ConfigDrive service, will not consume. The symptom is a clone that boots with none of its Cloud-Init settings applied and no error anywhere.

The pieces:

  1. Install cloudbase-init in the guest during audit mode, before generalising. Its installer offers to run sysprep at the end with its own unattend file; whether you use that or your own is a matter of taste, but use one or the other, not both.
  2. Attach a Cloud-Init drive to the template, as in the command block above.
  3. Set the per-clone values on the clone, not the template:
Configuration changeclone the template and set per-guest Cloud-Init values
TEMPLATE=9100
NEWID=151

qm clone "$TEMPLATE" "$NEWID" --name app-win-01 --full 1
qm set "$NEWID" \
--ciuser Administrator \
--ipconfig0 ip=192.0.2.61/24,gw=192.0.2.1 \
--nameserver 192.0.2.10 \
--searchdomain example.com

# Inspect exactly what the guest will be handed, before starting it
qm cloudinit dump "$NEWID" user
qm cloudinit dump "$NEWID" network

qm cloudinit dump is the verification step that can fail. It shows the generated configuration for user, network or meta without starting the guest, so a wrong gateway or a missing nameserver is visible before first boot rather than after a console session.

Key takeaways

  • Microsoft does not support moving or copying a Windows image to a different computer without Sysprep /generalize, explicitly including hard disk duplication, and explicitly including identical hardware. qm clone is duplication.
  • /generalize resets the SID, clears restore points and deletes event logs. The new SID is minted by the specialize pass on first boot, which is why the generalised image is safe to copy.
  • Non-generalised clones fail late and quietly: update-client collapse, KMS counts that never reach threshold, and patch compliance reports that are believed.
  • /mode:vm is faster but binds the image to a hardware profile — which on Proxmox includes the pinned machine version, firmware and controller.
  • qm template is one-way. Keep a pre-sysprep VM so that iterating on a template does not consume the rearm counter and does not require a rebuild.
  • Windows Cloud-Init means cloudbase-init, and Proxmox selects configdrive2 automatically from a Windows ostype. A guest left at ostype: other gets a nocloud drive and silently applies nothing.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A colleague argues that sysprep is unnecessary for a Proxmox template because every clone gets identical virtual hardware, a unique name and a unique domain account. What is the strongest correct response?

  2. Q2. Ninety Windows servers were cloned over six months from a VM that was never generalised. The update server shows twelve clients. What is the correct assessment of the remediation?

  3. Q3. Which of these belong in a Windows template before it is generalised? Select all that apply.

  4. Q4. On Proxmox, the Cloud-Init format handed to a Windows guest is selected automatically from the ostype field, and a Windows guest left at ostype other will receive a format cloudbase-init does not consume by default.

Passing score: 75%. Answers are checked in this browser.