P02-T07 - Windows 11 Golden Image
Objective
Build the reusable Windows 11 workstation golden image only after the domain foundation is ready, so workstation join validation is performed against the same image standard that future customer deployments will use.
This runbook covers:
- Proxmox VM creation for the transient Windows 11 image source
- manual Windows installation through the console until
QGAis available QEMU Guest Agent, VirtIO guest tools, andPowerShell 7.6.3- deployment of a reusable
Unattend.xmlbefore capture - image validation through
QGA - cleanup, DHCP reset, and
Sysprep - clone into the low-range template VMID
- conversion into the reusable workstation template
This runbook is an image-engineering exception to the normal Windows operations model in this guide. Here, QEMU Guest Agent is the primary transport after guest agent installation because the objective is repeatable image construction and validation, not day-to-day server or workstation administration.
For this phase, use QEMU Guest Agent as the authoritative execution channel as soon as it becomes available. Do not switch to RDP or WinRM as the primary build path.
Inputs
| Key | Value |
|---|---|
| Build VMID | 4030 |
| Final template VMID | 4001 |
| Template name | TPL-W11E |
| Approved edition for this lab | Windows 11 Enterprise LTSC |
| Supported editions in validation logic | Windows 11 Pro, Windows 11 Enterprise, Windows 11 Enterprise LTSC |
| Storage | local-zfs |
| Bridge | GEILLAN |
| Build VLAN | 110 |
| Build IP | 172.20.110.31/24 |
| Build gateway | 172.20.110.1 |
| Build DNS | 1.1.1.1, 9.9.9.9 |
| Windows ISO | Verify exact path on H1 before use |
| VirtIO ISO | local:iso/virtio-win.iso |
| PowerShell target version | 7.6.3 |
Target State
| Property | Value |
|---|---|
| Edition | Windows 11 Enterprise LTSC for this lab |
| Firmware | OVMF |
| Machine type | q35 |
| TPM | v2.0 present |
| Secure Boot | Enabled through enrolled keys |
| CPU | 2 vCPU |
| Memory | 4096 MB |
| Disk | 64 GB |
| NIC | VirtIO on GEILLAN tag 110 |
| Guest agent | Enabled |
| PowerShell | 7.6.3 |
| Domain joined | No |
| Static IP retained in template | No |
| Local build account retained as operator standard | No |
| First cloned boot behavior | completes business OOBE without manual console clicks |
Prechecks
- Complete
P03-T01,P03-T02,P03-T03, andP03-T04. - Confirm
QGArunner/root/gntech-qga/qga-run.shalready exists and works from earlier phases. - Confirm
VLAN 110build network is reachable and still intended for template construction. - Confirm the Windows 11 media is approved for this lab and licensing model.
- Do not plan to launch
SysprepthroughQGA,qm guest exec, or any workflow that runs it asLocal System.
If the runner is not already installed on H1, copy the complete repository version at this step:
Show full script: scripts/qga-run.sh
#!/usr/bin/env bash
set -Eeuo pipefail
usage() {
cat <<'EOF'
Usage:
qga-run.sh <VMID> <PowerShell-script.ps1> [timeout-seconds]
Example:
qga-run.sh 4000 validate-golden-image.ps1 900
EOF
}
fail() {
echo "ERROR: $*" >&2
exit 1
}
[[ $# -ge 2 && $# -le 3 ]] || {
usage
exit 2
}
VMID="$1"
SCRIPT_FILE="$2"
TIMEOUT_SECONDS="${3:-900}"
POLL_SECONDS=5
command -v qm >/dev/null 2>&1 || fail "qm was not found."
command -v jq >/dev/null 2>&1 || fail "jq was not found. Install it with: apt install jq"
command -v iconv >/dev/null 2>&1 || fail "iconv was not found."
command -v base64 >/dev/null 2>&1 || fail "base64 was not found."
[[ -f "$SCRIPT_FILE" ]] || fail "PowerShell script not found: $SCRIPT_FILE"
[[ "$TIMEOUT_SECONDS" =~ ^[0-9]+$ ]] || fail "Timeout must be an integer."
qm status "$VMID" >/dev/null 2>&1 || fail "VMID $VMID does not exist."
[[ "$(qm status "$VMID" | awk '{print $2}')" == "running" ]] || fail "VMID $VMID is not running."
qm guest cmd "$VMID" ping >/dev/null 2>&1 || fail "QEMU Guest Agent is not responding for VMID $VMID."
ENCODED_COMMAND="$(iconv -f UTF-8 -t UTF-16LE "$SCRIPT_FILE" | base64 -w 0)"
START_RESPONSE="$(
qm guest exec "$VMID" -- powershell.exe -NoLogo -NoProfile -NonInteractive \
-ExecutionPolicy Bypass -EncodedCommand "$ENCODED_COMMAND"
)"
if jq -e '.exited == 1' >/dev/null 2>&1 <<<"$START_RESPONSE"; then
FINAL_RESPONSE="$START_RESPONSE"
else
PID="$(jq -r '.pid // empty' <<<"$START_RESPONSE")"
[[ -n "$PID" ]] || fail "Unable to determine guest execution PID. Response: $START_RESPONSE"
echo "Guest execution started with PID $PID."
START_TIME="$(date +%s)"
while true; do
FINAL_RESPONSE="$(qm guest exec-status "$VMID" "$PID")"
jq -e '.exited == 1' >/dev/null 2>&1 <<<"$FINAL_RESPONSE" && break
CURRENT_TIME="$(date +%s)"
if (( CURRENT_TIME - START_TIME >= TIMEOUT_SECONDS )); then
echo "Timeout reached; the guest process may still be running."
echo "Check it with: qm guest exec-status $VMID $PID"
exit 124
fi
sleep "$POLL_SECONDS"
done
fi
STDOUT="$(jq -r '."out-data" // empty' <<<"$FINAL_RESPONSE")"
STDERR="$(jq -r '."err-data" // empty' <<<"$FINAL_RESPONSE")"
EXIT_CODE="$(jq -r '.exitcode // 1' <<<"$FINAL_RESPONSE")"
[[ -z "$STDOUT" ]] || printf '%s\n' "$STDOUT"
[[ -z "$STDERR" ]] || printf '%s\n' "$STDERR" >&2
[[ "$EXIT_CODE" -eq 0 ]] || fail "PowerShell script failed with exit code $EXIT_CODE."
echo "QGA execution completed successfully."
install -d -m 700 /root/gntech-qga
install -m 700 scripts/qga-run.sh /root/gntech-qga/qga-run.sh
bash -n /root/gntech-qga/qga-run.sh
Design Guardrails
Sysprepon Windows 11 24H2 or later and Windows Server 2025 must not be executed under theLocal Systemsecurity context. Microsoft documents that this can leave the deployed OS withexplorer.exeand other XAML-dependent shell components broken after sign-in.QGAremains the primary non-interactive execution channel for build steps, validation, and post-clone automation, but the finalSyspreplaunch must come from an interactive local administrator session inside the guest.- The golden image must include a staged
Unattend.xmlso first boot after cloning does not stop in consumer-styleOOBEprompts. - The approved Windows 11 pattern in this guide includes temporary local administrator autologon with
LogonCount=1, followed by immediate removal ofAutoAdminLogonthroughFirstLogonCommands.
Current State Verification
- Verify
VMID 4030is unused onH1:
- Verify
VMID 4001is also unused before reserving it for the final template:
- Verify the exact ISO inventory on
H1before writing anyqm setcommands:
Required interpretation:
- Record the exact Windows 11 ISO path from the storage inventory.
- Confirm
virtio-win.isoexists. - Do not continue with guessed ISO filenames.
For this lab, prefer the approved Windows 11 Enterprise LTSC media. The build logic is compatible with Windows 11 Pro, Windows 11 Enterprise, and Windows 11 Enterprise LTSC, but the lab standard should still declare one approved edition.
Execution
Phase A - Create the Proxmox VM
On H1, after confirming the exact Windows ISO path, create the VM:
VMID=4030
WIN11_ISO='local:iso/REPLACE_WITH_EXACT_WINDOWS11_LTSC_OR_OTHER_APPROVED_ISO.iso'
qm create "$VMID" \
--name TPL-W11E \
--description "Windows 11 workstation golden image" \
--ostype win11 \
--machine q35 \
--bios ovmf \
--cpu host \
--sockets 1 \
--cores 2 \
--memory 4096 \
--balloon 0 \
--scsihw virtio-scsi-single \
--agent enabled=1,fstrim_cloned_disks=1 \
--net0 virtio,bridge=GEILLAN,tag=110,firewall=0 \
--vga std \
--tablet 1 \
--onboot 0 \
--tags "golden-image;windows;win11;build"
qm set "$VMID" \
--efidisk0 local-zfs:1,efitype=4m,pre-enrolled-keys=1
qm set "$VMID" \
--tpmstate0 local-zfs:1,version=v2.0
qm set "$VMID" \
--scsi0 local-zfs:64,discard=on,iothread=1,ssd=1
qm set "$VMID" \
--ide2 "$WIN11_ISO",media=cdrom
qm set "$VMID" \
--ide3 local:iso/virtio-win.iso,media=cdrom
qm set "$VMID" --boot order=ide2
qm config "$VMID"
qm start "$VMID"
qm status "$VMID"
Phase B - Manual installation until QGA is available
Use the Proxmox console for the initial Windows installation.
- Install the approved Windows 11 edition for this lab.
- If no disk is visible, load the VirtIO storage driver from the VirtIO ISO:
- prefer
vioscsi\w11\amd64 - if that path does not exist, use the closest supported
amd64Windows client path present on the ISO - Complete setup with a temporary local administrator account for the build.
- Do not join the domain.
- After first sign-in, set the temporary build network:
- IP:
172.20.110.31 - Mask:
255.255.255.0 - Gateway:
172.20.110.1 - DNS 1:
1.1.1.1 - DNS 2:
9.9.9.9
Phase C - Guest agent and base tooling
Open an elevated PowerShell session inside the Windows 11 guest and run:
$VirtioDrive = 'E:'
$QgaInstaller = Join-Path $VirtioDrive 'guest-agent\qemu-ga-x86_64.msi'
if (-not (Test-Path $QgaInstaller)) {
throw "QEMU Guest Agent installer not found at $QgaInstaller"
}
Start-Process msiexec.exe `
-ArgumentList "/i `"$QgaInstaller`" /qn /norestart" `
-Wait `
-NoNewWindow
$VirtioToolsInstaller = Join-Path $VirtioDrive 'virtio-win-gt-x64.msi'
if (-not (Test-Path $VirtioToolsInstaller)) {
throw "VirtIO guest tools installer not found at $VirtioToolsInstaller"
}
Start-Process msiexec.exe `
-ArgumentList "/i `"$VirtioToolsInstaller`" /qn /norestart" `
-Wait `
-NoNewWindow
Set-Service QEMU-GA -StartupType Automatic
Start-Service QEMU-GA
Restart-Computer
Phase D - Verify QGA from H1
After reboot:
until qm guest cmd 4030 ping >/dev/null 2>&1; do
echo "Waiting for QEMU Guest Agent on TPL-W11E..."
sleep 5
done
qm guest cmd 4030 get-osinfo
qm guest cmd 4030 network-get-interfaces
The interface data must include 172.20.110.31.
Phase E - Install PowerShell 7.6.3 and workstation build settings
Create /root/gntech-qga/build-tpl-w11e-stage1.ps1 on H1:
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
Write-Output '=== PowerShell 7 Download ==='
New-Item -ItemType Directory -Path 'C:\Temp' -Force | Out-Null
curl.exe -L 'https://github.com/PowerShell/PowerShell/releases/download/v7.6.3/PowerShell-7.6.3-win-x64.msi' -o 'C:\Temp\PowerShell-7.6.3-win-x64.msi'
Write-Output '=== PowerShell 7 Install ==='
$Arguments = @(
'/i',
'C:\Temp\PowerShell-7.6.3-win-x64.msi',
'/qn',
'/norestart',
'ADD_PATH=1',
'ENABLE_PSREMOTING=1',
'REGISTER_MANIFEST=1',
'USE_MU=1',
'ENABLE_MU=1',
'DISABLE_TELEMETRY=1'
)
$Result = Start-Process msiexec.exe -ArgumentList $Arguments -Wait -PassThru -NoNewWindow
Write-Output "PowerShell MSI exit code: $($Result.ExitCode)"
if ($Result.ExitCode -ne 0) {
exit $Result.ExitCode
}
Write-Output '=== Validation ==='
Write-Output "Hostname: $env:COMPUTERNAME"
Write-Output "PowerShell 7: $(& 'C:\Program Files\PowerShell\7\pwsh.exe' -NoProfile -Command '$PSVersionTable.PSVersion.ToString()')"
Get-Service QEMU-GA | ForEach-Object {
Write-Output "QEMU-GA: $($_.Status) / $($_.StartType)"
}
Execute:
Phase F - Validate the Windows 11 image before cleanup
Create /root/gntech-qga/validate-tpl-w11e.ps1 on H1:
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$Failures = [System.Collections.Generic.List[string]]::new()
$ApprovedEdition = 'Windows 11 Enterprise LTSC'
$AllowedEditions = @(
'Windows 11 Pro',
'Windows 11 Enterprise',
'Windows 11 Enterprise LTSC'
)
Write-Output '=== Operating system ==='
$OperatingSystem = Get-CimInstance Win32_OperatingSystem
Write-Output "Caption: $($OperatingSystem.Caption)"
Write-Output "Version: $($OperatingSystem.Version)"
Write-Output "Build: $($OperatingSystem.BuildNumber)"
Write-Output "Approved Edition: $ApprovedEdition"
Write-Output "Allowed Editions: $($AllowedEditions -join ', ')"
$EditionAllowed = $false
foreach ($Edition in $AllowedEditions) {
if ($OperatingSystem.Caption -like "*$Edition*") {
$EditionAllowed = $true
}
}
if (-not $EditionAllowed) {
$Failures.Add("Unexpected Windows 11 edition: $($OperatingSystem.Caption)")
}
if ($OperatingSystem.Caption -notlike "*$ApprovedEdition*") {
Write-Output "Edition Warning: Current caption does not match the approved lab edition '$ApprovedEdition'."
}
Write-Output '=== QGA and services ==='
Get-Service QEMU-GA | ForEach-Object {
Write-Output "QEMU-GA: $($_.Status) / $($_.StartType)"
if ($_.Status.ToString() -ne 'Running') {
$Failures.Add('QEMU-GA is not running.')
}
}
Write-Output '=== PowerShell 7 ==='
$PwshPath = 'C:\Program Files\PowerShell\7\pwsh.exe'
if (-not (Test-Path $PwshPath)) {
$Failures.Add('PowerShell 7 executable is missing.')
} else {
$PwshVersion = & $PwshPath -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'
Write-Output "PowerShell 7 version: $PwshVersion"
if ($PwshVersion -ne '7.6.3') {
$Failures.Add("Unexpected PowerShell 7 version: $PwshVersion")
}
}
Write-Output '=== TPM ==='
Get-Tpm | ForEach-Object {
Write-Output "TPM Present: $($_.TpmPresent)"
Write-Output "TPM Ready: $($_.TpmReady)"
if (-not $_.TpmPresent) {
$Failures.Add('TPM is not present.')
}
}
Write-Output '=== Domain state ==='
$ComputerSystem = Get-CimInstance Win32_ComputerSystem
Write-Output "PartOfDomain: $($ComputerSystem.PartOfDomain)"
if ($ComputerSystem.PartOfDomain) {
$Failures.Add('The workstation image is already domain joined.')
}
Write-Output '=== Network state ==='
Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | ForEach-Object {
Write-Output "IPv4: $($_.IPAddress) / PrefixOrigin=$($_.PrefixOrigin)"
}
if ($Failures.Count -gt 0) {
Write-Output '=== Failures ==='
$Failures | ForEach-Object { Write-Output $_ }
exit 1
}
Write-Output 'Validation completed successfully.'
Execute:
Phase G - Cleanup, DHCP reset, and Sysprep
Create /root/gntech-qga/finalize-tpl-w11e.ps1 on H1:
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
Write-Output 'Running component cleanup.'
$Dism = Start-Process `
-FilePath 'dism.exe' `
-ArgumentList '/Online', '/Cleanup-Image', '/StartComponentCleanup' `
-Wait `
-PassThru `
-NoNewWindow
if ($Dism.ExitCode -notin 0, 3010) {
throw "DISM cleanup failed with exit code $($Dism.ExitCode)."
}
Write-Output 'Cleaning temporary files.'
$TemporaryPaths = @(
'C:\Windows\Temp\*',
"$env:TEMP\*",
'C:\Windows\SoftwareDistribution\Download\*'
)
foreach ($Path in $TemporaryPaths) {
Remove-Item -Path $Path -Recurse -Force -ErrorAction SilentlyContinue
}
Write-Output 'Resetting network adapter to DHCP.'
$Adapter = Get-NetAdapter |
Where-Object Status -eq 'Up' |
Sort-Object ifIndex |
Select-Object -First 1
if (-not $Adapter) {
throw 'No active network adapter was found.'
}
Set-NetIPInterface `
-InterfaceIndex $Adapter.ifIndex `
-AddressFamily IPv4 `
-Dhcp Enabled
Get-NetIPAddress `
-InterfaceIndex $Adapter.ifIndex `
-AddressFamily IPv4 `
-ErrorAction SilentlyContinue |
Where-Object PrefixOrigin -eq 'Manual' |
Remove-NetIPAddress -Confirm:$false -ErrorAction SilentlyContinue
Set-DnsClientServerAddress `
-InterfaceIndex $Adapter.ifIndex `
-ResetServerAddresses
Write-Output 'Removing build-network default routes from persistent and active stores.'
foreach ($policyStore in 'PersistentStore','ActiveStore') {
Get-NetRoute `
-PolicyStore $policyStore `
-InterfaceIndex $Adapter.ifIndex `
-AddressFamily IPv4 `
-DestinationPrefix '0.0.0.0/0' `
-ErrorAction SilentlyContinue |
Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue
}
Write-Output 'Stopping services that should not remain active during capture.'
Stop-Service wuauserv -Force -ErrorAction SilentlyContinue
Write-Output 'Finalization completed successfully.'
Execute:
Create C:\Windows\Panther\Unattend\Unattend.xml before capture.
Create /root/gntech-qga/stage-tpl-w11e-unattend.ps1 on H1:
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$UnattendDir = 'C:\Windows\Panther\Unattend'
$UnattendPath = Join-Path $UnattendDir 'Unattend.xml'
New-Item -ItemType Directory -Path $UnattendDir -Force | Out-Null
$UnattendXml = @'
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" versionScope="nonSxS">
<InputLocale>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" versionScope="nonSxS">
<TimeZone>SA Western Standard Time</TimeZone>
<RegisteredOrganization>GNTECH</RegisteredOrganization>
<RegisteredOwner>GNTECH</RegisteredOwner>
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideLocalAccountScreen>true</HideLocalAccountScreen>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<ProtectYourPC>3</ProtectYourPC>
</OOBE>
</component>
</settings>
</unattend>
'@
Set-Content -Path $UnattendPath -Value $UnattendXml -Encoding UTF8
Write-Output '=== Unattend Staged ==='
Write-Output "Path: $UnattendPath"
Get-Content $UnattendPath | ForEach-Object { $_ }
Execute:
Important interpretation:
- This answer file is intentionally edition-agnostic across the supported lab set:
Pro,Enterprise, andEnterprise LTSC. - The file is there to suppress first-boot
OOBEfriction. It is not there to join the domain, inject static networking, or create a permanent deployment operator account. - In the validated lab, the answer file also required temporary local administrator autologon so the first cloned boot completed without interactive prompts.
Launch Sysprep correctly
Do not run Sysprep from QGA.
Instead:
- Open the Proxmox console for
4030. - Sign in with the temporary local build administrator.
- Open an elevated
PowerShellorcmd.exe. - Launch:
Start-Process `
'C:\Windows\System32\Sysprep\Sysprep.exe' `
-ArgumentList '/generalize','/oobe','/shutdown','/mode:vm' `
-Wait
Or from cmd.exe:
Only after the VM powers off should you continue on H1.
Wait for shutdown:
until [ "$(qm status 4030 | awk '{print $2}')" = "stopped" ]; do
echo "Waiting for Sysprep shutdown..."
sleep 5
done
Phase H - Clone-test the generalized image, then convert to the final template VMID
If you created any pre-Sysprep or rollback snapshots on the build VM, they are valid only for the build stage. Proxmox template conversion must happen on a VM with no snapshots in its chain.
Verify snapshot state on the build VM:
Expected interpretation:
- If
4030still has snapshots, do not convert it directly to a template. - Keep
4030as the transient build VM and clone the generalized image into4001. - Before converting
4001, verify4001has no snapshots. If any snapshot exists on4001, delete it first.
New standard:
- do not convert a generalized source VM directly into a template without a clone test
- first create a temporary validation clone from the generalized source
- validate first boot and guest readiness
- then create the final template VMID
Example validation pattern:
qm clone 4030 4041 \
--name TEST-W11E \
--full 1 \
--storage local-zfs
qm set 4041 --net0 virtio,bridge=GEILLAN,tag=30,firewall=0
qm start 4041
Only after the validation clone passes should you create the final template VMID.
Validation requirements for 4041:
OOBEInProgress: 0SystemSetupInProgress: 0ImageState: IMAGE_STATE_COMPLETE- no product key or residual first-boot prompt appears
- the clone receives a valid
DHCPlease on the validation network - the only default gateway is the gateway delivered by DHCP; no build-network route remains in
PersistentStore PowerShell 7.6.3remains installedQEMU-GAremains running
Clone the stopped, generalized build VM into the final template VMID:
In the validated lab, the generalized source was first rebuilt and tested on 4014, then the approved final template slot 4001 was replaced only after the clone test passed.
Detach installation media on the final VMID, set metadata, then convert:
qm set 4001 --delete ide2
qm set 4001 --delete ide3
qm set 4001 \
--boot order=scsi0 \
--description "Windows 11 Enterprise LTSC golden image. Validated, generalized with Sysprep, QEMU Guest Agent enabled. Do not start directly."
qm listsnapshot 4001
qm template 4001
qm config 4001
qm status 4001
Validation
qm config 4001shows:template: 1agent: enabled=1bios: ovmfmachine: pc-q35tpmstate0presentboot: order=scsi0name: TPL-W11E- a post-capture clone boot test passed before conversion
qm status 4001showsstopped.qm listsnapshot 4001shows no snapshots beforeqm template 4001.QEMU Guest Agentresponds throughqm guest cmd 4030 pingbeforeSysprep.PowerShell 7.6.3is installed.- TPM is present in the guest.
C:\Windows\Panther\Unattend\Unattend.xmlexists beforeSysprep.Sysprepwas launched from an interactive local administrator session, not throughQGA.- the post-capture clone does not prompt for any residual first-boot wizard step
Get-NetRoute -PolicyStore PersistentStore -DestinationPrefix '0.0.0.0/0'does not expose the build gateway
Operational Notes
- The July 18, 2026 audit found that current template
4001still retains persistent build gateway172.20.110.1. The corrected cleanup above must be applied during an interactive Sysprep recapture before the template is considered clean again. -
P03-T06can remove that exact legacy route through an explicit, validated onboarding switch, but that is a controlled lab remediation rather than a replacement for recapturing the source image. -
If a deployed clone still shows the Windows 11 black-shell symptom after first logon, treat that as an exception path and investigate whether
Sysprepwas accidentally executed asSystem. - For temporary recovery of a broken shell on an already deployed machine, the following commands were validated in this lab:
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
Stop-Process -Name sihost -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 5
Start-Process 'C:\Windows\explorer.exe'
Windows 11 Enterprise LTSC.
- ISOs are detached after finalization.
- The template is not started again after Sysprep.
Evidence
- Output of
qm config 4030 - Output of
qm status 4030 - Output of
qm listsnapshot 4030 - Output of
qm config 4001 - Output of
qm status 4001 - Output of
validate-tpl-w11e.ps1 - Output of validation from the post-capture clone
- Output showing
PowerShell MSI exit code: 0 - Evidence that the workstation image was not domain joined before capture
Rollback
- Destroy and recreate
VMID 4030if firmware, TPM, disks, or ISO bindings are wrong. - Restore from a pre-Sysprep snapshot if cleanup must be redone.
- Delete any snapshot on the final template candidate before running
qm template. - Never convert a Windows 11 image to template if
QGA, TPM, or non-domain-joined validation failed.