Skip to content

Commit 7f460d2

Browse files
committed
handle Win11 25H2 Setup pickers, CD reboot loop, hidden PBUTTONACTION
Four changes found during end-to-end build validation on a fresh GCP VM: 1. autounattend.xml: unhide PBUTTONACTION before powercfg /setacvalueindex. On Win11 25H2 the physical power-button power setting is hidden by default (Attributes=1), so the friendly alias never resolves and the powercfg commands silently no-op. Result: verify.ps1's "ACPI power button = shutdown (3)" check always fails. Fix: run `powercfg /attributes ... -ATTRIB_HIDE` first, then reference the setting by full GUID. Bumps the FirstLogonCommands count from 26 to 27. 2. scripts/remediate.ps1: same unhide+full-GUID treatment so the remediate loop can actually repair the setting. 3. build.yml + README quirk #5: Win11 24H2+ SetupHost.exe shows two mandatory "Select language settings" + "Select keyboard settings" screens BEFORE it reads autounattend.xml. Nothing in the unattend file (SetupUILanguage, SystemLocale, UILanguage, etc.) skips them. Fix: spray `sendkey alt-n` every 3 s via the QEMU monitor until the qcow2 starts growing (signal that Setup has advanced past the pickers and begun partitioning + file copy). Alt+N activates the Next button by accelerator regardless of where keyboard focus is, which matters because a stray Enter can land on the "Support" link and open a modal "Unable to open link" dialog that then swallows subsequent Enters. 4. build.yml + README quirk #6: eject all three CDs from the QEMU monitor before the first post-install reboot. Because cd0 is pinned to bootindex=0, a warm reboot with the install media still attached hangs OVMF BDS in an infinite Boot0001 retry loop (bootmgr on the CD renders "Press any key", times out, OVMF marks Boot0001 failed, then BDS restarts from Boot0001 instead of falling through to Boot0002). Two vCPUs sit pegged at 100% forever and `wait_for_ssh` never comes back. `eject -f cd0/cd1/cd2` via the monitor removes them from the candidate list and OVMF picks Boot0002 on the next pass. End-to-end verify reaches 23/24 checks. The one remaining failure (EMS-SAC.Tools FoD) is a Microsoft packaging issue: the capability is NotPresent on EN-Intl Win11 Pro (ImageIndex=6) and DISM can't fetch the cab from Windows Update. Base EMS/SAC serial console still works because it lives in the kernel; the FoD only adds additional admin CLI tools.
1 parent 5e54a01 commit 7f460d2

4 files changed

Lines changed: 109 additions & 17 deletions

File tree

.github/workflows/build.yml

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,34 @@ jobs:
136136
sleep $delay
137137
echo 'sendkey ret' | nc -w 1 -q 1 127.0.0.1 4444 >/dev/null
138138
done
139-
echo "Keys sent"
139+
echo "Enter keys sent"
140+
141+
- name: Click past Win11 25H2 Setup language + keyboard pickers
142+
# Windows 11 24H2+ modernized Setup (SetupHost.exe) shows a mandatory
143+
# "Select language settings" screen followed by "Select keyboard settings"
144+
# BEFORE it reads autounattend.xml. Nothing you can put in the unattend
145+
# XML skips these two screens -- not SetupUILanguage, not SystemLocale,
146+
# nothing. See "Quirk #5" in README.md.
147+
#
148+
# Fix: blindly press Alt+N (the underlined accelerator for the Next
149+
# button) every few seconds. The pickers pre-populate with the ISO's
150+
# locale so "Next" accepts sane defaults. Stop once the disk starts
151+
# growing -- that signals Setup has read autounattend.xml and begun
152+
# partitioning / file copy.
153+
run: |
154+
for i in $(seq 1 60); do
155+
sleep 3
156+
echo 'sendkey alt-n' | nc -w 1 -q 1 127.0.0.1 4444 >/dev/null
157+
DISK_K=$(du -k "$QCOW2_NAME" | cut -f1)
158+
if [ "$DISK_K" -gt 500000 ]; then
159+
echo "Setup past pickers (disk=${DISK_K}K at iter=$i), stopping Alt+N spray"
160+
break
161+
fi
162+
done
163+
if [ "$DISK_K" -le 500000 ]; then
164+
echo "::error::Disk still ${DISK_K}K after 60 Alt+N presses, Setup did not advance"
165+
exit 1
166+
fi
140167
141168
- name: Wait for install.success marker
142169
run: |
@@ -163,6 +190,26 @@ jobs:
163190
echo "::error::Timed out after ${MAX_WAIT}s"
164191
exit 1
165192
193+
- name: Eject install CDs via QEMU monitor
194+
# After the install finishes, the CDs MUST be ejected from the QEMU
195+
# monitor before the first VM reboot. Reason: we pin the Windows ISO
196+
# to bootindex=0 so OVMF always tries Boot0001 first; on the initial
197+
# install that is exactly what we want (key spray defeats "Press any
198+
# key to boot from CD"), but on a warm reboot Windows bootmgr still
199+
# renders the prompt, still times out, and OVMF then *re-loops* on
200+
# Boot0001 instead of falling through to the disk entry. Result: the
201+
# VM sits forever at two pegged CPUs in OVMF BDS retrying the CD, the
202+
# post-reboot `wait_for_ssh` hits its 5 min timeout and the job fails.
203+
#
204+
# Using `eject` + `change ... none` via the monitor removes the CDs
205+
# from the boot order and OVMF picks Boot0002 (Windows Boot Manager
206+
# on the virtio-blk disk) immediately on the next reboot.
207+
run: |
208+
printf 'eject -f cd0\n' | nc -w 1 -q 1 127.0.0.1 4444
209+
printf 'eject -f cd1\n' | nc -w 1 -q 1 127.0.0.1 4444
210+
printf 'eject -f cd2\n' | nc -w 1 -q 1 127.0.0.1 4444 2>/dev/null || true
211+
printf 'info block\n' | nc -w 1 -q 1 127.0.0.1 4444
212+
166213
- name: Verify and remediate
167214
run: |
168215
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null"

README.md

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,32 @@ done
193193

194194
Fifteen presses from +2 s through +17 s cover cold QEMU start + OVMF boot time.
195195

196+
**5. Windows 11 24H2+ Setup (SetupHost.exe) shows two mandatory picker screens before it reads `autounattend.xml`.** Starting with 24H2, Microsoft replaced classic `setup.exe` with a new `SetupHost.exe`-based modernized installer. The very first two screens — **"Select language settings"** followed by **"Select keyboard settings"** — are drawn by the modern UI *before* `SetupHost` ever opens the unattend file, so there is nothing you can put inside `Microsoft-Windows-International-Core-WinPE` (not `InputLocale`, not `SystemLocale`, not `UILanguage`, not `SetupUILanguage`) that will skip them. They must be clicked.
197+
198+
Both screens pre-populate with the ISO's native locale ("English (United Kingdom)" for the EN-International ISO), so accepting the defaults is fine. The Next button exposes an underlined `N` accelerator, so **`Alt+N`** activates it regardless of where keyboard focus currently is. Spray Alt+N every few seconds and bail out as soon as the disk starts growing (that's Setup reading autounattend and starting the partitioner):
199+
200+
```bash
201+
for i in $(seq 1 60); do
202+
sleep 3
203+
echo 'sendkey alt-n' | nc -w 1 -q 1 127.0.0.1 4444
204+
DISK_K=$(du -k windows-11-25h2.qcow2 | cut -f1)
205+
if [ "$DISK_K" -gt 500000 ]; then
206+
echo "Setup past pickers, disk=${DISK_K}K"
207+
break
208+
fi
209+
done
210+
```
211+
212+
Do **not** send bare `Enter` keys during this phase: if `Enter` ever lands while keyboard focus is on the on-screen "Support" link (which happens when the key falls through from the bootmgr spray into the Setup UI) it opens a modal "Unable to open link" dialog that then swallows every subsequent Enter. `Alt+N` always targets the Next button, not the focused link.
213+
214+
**6. Eject the install CDs from the QEMU monitor before the first post-install reboot.** Because we pin the Windows ISO to `bootindex=0` so OVMF always tries it first (required to defeat quirk #4 on the initial install), a warm reboot of the installed VM falls into an infinite BDS loop: bootmgr on the CD renders "Press any key" for 5 s, bootmgr exits via timeout, OVMF marks Boot0001 failed, then OVMF re-enters BDS and tries Boot0001 *again* instead of Boot0002 (Windows Boot Manager on the disk). Two CPUs sit pegged at 100 % executing OVMF BDS forever and SSH never comes back. Ejecting the CDs through the monitor between the "install.success" marker and `shutdown /r` removes Boot0001 from the candidate list; OVMF then picks Boot0002 on the very first BDS pass and Windows boots cleanly:
215+
216+
```bash
217+
printf 'eject -f cd0\n' | nc -w 1 -q 1 127.0.0.1 4444
218+
printf 'eject -f cd1\n' | nc -w 1 -q 1 127.0.0.1 4444
219+
printf 'eject -f cd2\n' | nc -w 1 -q 1 127.0.0.1 4444
220+
```
221+
196222
#### Build steps
197223

198224
```bash
@@ -242,6 +268,14 @@ for delay in 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1; do
242268
sleep $delay
243269
echo 'sendkey ret' | nc -w 1 -q 1 127.0.0.1 4444
244270
done
271+
272+
# 7. Click past Win11 25H2 Setup language + keyboard pickers (quirk #5)
273+
for i in $(seq 1 60); do
274+
sleep 3
275+
echo 'sendkey alt-n' | nc -w 1 -q 1 127.0.0.1 4444
276+
DISK_K=$(du -k windows-11-25h2.qcow2 | cut -f1)
277+
if [ "$DISK_K" -gt 500000 ]; then break; fi
278+
done
245279
```
246280

247281
Snapshot the screen anytime with `screendump`:
@@ -306,7 +340,7 @@ The included [`autounattend.xml`](autounattend.xml) drives the install across th
306340
- **International-Core**: `InputLocale=0409:00000409` only. The component must be present here for Windows 11 25H2 OOBE to skip the country / keyboard selection screens, but we deliberately do not pin `SystemLocale`/`UILanguage`/`UserLocale` so the image default wins.
307341
- **OOBE**: hides EULA, online account, wireless setup.
308342
- **User account**: local admin `cocoon` with auto-logon (password base64-encoded in XML).
309-
- **FirstLogonCommands**: 26 commands.
343+
- **FirstLogonCommands**: 27 commands.
310344

311345
| Order | Action | Notes |
312346
|--------|------------------------------|-------|
@@ -317,15 +351,16 @@ The included [`autounattend.xml`](autounattend.xml) drives the install across th
317351
| 7 | **Hibernate** | `powercfg /h off` |
318352
| 8-10 | **SAC / EMS** | `bcdedit /emssettings emsport:1 emsbaudrate:115200`, `/ems on`, `/bootems on` |
319353
| 11 | **TermService** | Set to auto-start |
320-
| 12 | **EMS-SAC Tools** | `Add-WindowsCapability Windows.Desktop.EMS-SAC.Tools~~~~0.0.1.0` — wrapped in `Start-Job` + `Wait-Job -Timeout 1200` so a hung FoD download from Windows Update cannot block the rest of the sequence indefinitely |
354+
| 12 | **EMS-SAC Tools** | `Add-WindowsCapability Windows.Desktop.EMS-SAC.Tools~~~~0.0.1.0` — wrapped in `Start-Job` + `Wait-Job -Timeout 1200` so a hung FoD download from Windows Update cannot block the rest of the sequence indefinitely. This FoD is NotPresent on some Pro SKUs (EN-Intl ISO, ImageIndex=6 Pro) and the install can fail with no network source — base EMS/SAC serial console still works because it is built into the kernel; the FoD only adds additional admin CLI tools |
321355
| 13 | **Network profile** | Set to Private (required before WinRM AllowUnencrypted) |
322356
| 14-17 | **WinRM** | Enable PS Remoting, AllowUnencrypted, Basic auth, firewall on 5985 |
323357
| 18 | **Hostname** | Force `Rename-Computer` to `COCOON-VM` (specialize ComputerName unreliable on 25H2) |
324358
| 19 | **virtio-win guest tools** | Silent install `virtio-win-guest-tools.exe /S` from CD-ROM — drivers + QEMU Guest Agent + spice agent in one shot |
325-
| 20-22 | **ACPI power button = Shut down** | `PBUTTONACTION=3` for AC + DC power schemes |
326-
| 23-24 | **Shutdown optimization** | `WaitToKillServiceTimeout=5000`, `DisableShutdownNamedPipeCheck=1` |
327-
| 25 | **Shutdown without logon** | Allow remote `shutdown /s /t 0` with no user logged in |
328-
| 26 | **Install marker** | `cmd /c "echo %date% %time% > C:\install.success"` |
359+
| 20 | **Unhide PBUTTONACTION** | `powercfg /attributes ... -ATTRIB_HIDE` — on Win11 25H2 the physical power-button setting is hidden (Attributes=1), so every subsequent `powercfg /setacvalueindex SUB_BUTTONS PBUTTONACTION ...` silently no-ops until the setting is unhidden |
360+
| 21-23 | **ACPI power button = Shut down** | `PBUTTONACTION=3` for AC + DC power schemes, referenced by full GUID because the friendly alias does not resolve while the setting is hidden |
361+
| 24-25 | **Shutdown optimization** | `WaitToKillServiceTimeout=5000`, `DisableShutdownNamedPipeCheck=1` |
362+
| 26 | **Shutdown without logon** | Allow remote `shutdown /s /t 0` with no user logged in |
363+
| 27 | **Install marker** | `cmd /c "echo %date% %time% > C:\install.success"` |
329364

330365
## Post-clone networking
331366

autounattend.xml

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,22 @@
8787
<SynchronousCommand wcm:action="add"><Order>18</Order><CommandLine>powershell -Command "Rename-Computer -NewName 'COCOON-VM' -Force"</CommandLine></SynchronousCommand>
8888
<!-- V10: Silent install virtio-win guest tools (drivers + QEMU Guest Agent + spice agent) -->
8989
<SynchronousCommand wcm:action="add"><Order>19</Order><CommandLine>cmd /c "if exist D:\virtio-win-guest-tools.exe (D:\virtio-win-guest-tools.exe /S) else if exist E:\virtio-win-guest-tools.exe (E:\virtio-win-guest-tools.exe /S)"</CommandLine></SynchronousCommand>
90-
<!-- V10: Set ACPI power button action to "Shut down" (default may be Sleep/Do nothing) -->
91-
<SynchronousCommand wcm:action="add"><Order>20</Order><CommandLine>powercfg /setacvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3</CommandLine></SynchronousCommand>
92-
<SynchronousCommand wcm:action="add"><Order>21</Order><CommandLine>powercfg /setdcvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3</CommandLine></SynchronousCommand>
93-
<SynchronousCommand wcm:action="add"><Order>22</Order><CommandLine>powercfg /setactive SCHEME_CURRENT</CommandLine></SynchronousCommand>
90+
<!-- V12: Set ACPI power button action to "Shut down" (default is Sleep/Do nothing).
91+
On Win11 25H2 the PBUTTONACTION setting is hidden by default (Attributes=1),
92+
so powercfg /setacvalueindex silently no-ops when using the friendly alias.
93+
Fix: unhide it first, then reference by full GUID (the alias resolver also
94+
fails for hidden settings). -->
95+
<SynchronousCommand wcm:action="add"><Order>20</Order><CommandLine>powercfg /attributes 4f971e89-eebd-4455-a8de-9e59040e7347 7648efa3-dd9c-4e3e-b566-50f929386280 -ATTRIB_HIDE</CommandLine></SynchronousCommand>
96+
<SynchronousCommand wcm:action="add"><Order>21</Order><CommandLine>powercfg /setacvalueindex SCHEME_CURRENT SUB_BUTTONS 7648efa3-dd9c-4e3e-b566-50f929386280 3</CommandLine></SynchronousCommand>
97+
<SynchronousCommand wcm:action="add"><Order>22</Order><CommandLine>powercfg /setdcvalueindex SCHEME_CURRENT SUB_BUTTONS 7648efa3-dd9c-4e3e-b566-50f929386280 3</CommandLine></SynchronousCommand>
98+
<SynchronousCommand wcm:action="add"><Order>23</Order><CommandLine>powercfg /setactive SCHEME_CURRENT</CommandLine></SynchronousCommand>
9499
<!-- V10: Speed up shutdown via SSH/WinRM -->
95-
<SynchronousCommand wcm:action="add"><Order>23</Order><CommandLine>reg add "HKLM\SYSTEM\CurrentControlSet\Control" /v WaitToKillServiceTimeout /t REG_SZ /d 5000 /f</CommandLine></SynchronousCommand>
96-
<SynchronousCommand wcm:action="add"><Order>24</Order><CommandLine>reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v DisableShutdownNamedPipeCheck /t REG_DWORD /d 1 /f</CommandLine></SynchronousCommand>
100+
<SynchronousCommand wcm:action="add"><Order>24</Order><CommandLine>reg add "HKLM\SYSTEM\CurrentControlSet\Control" /v WaitToKillServiceTimeout /t REG_SZ /d 5000 /f</CommandLine></SynchronousCommand>
101+
<SynchronousCommand wcm:action="add"><Order>25</Order><CommandLine>reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v DisableShutdownNamedPipeCheck /t REG_DWORD /d 1 /f</CommandLine></SynchronousCommand>
97102
<!-- V10: Allow shutdown without logged-on user (required for SSH/WinRM remote shutdown) -->
98-
<SynchronousCommand wcm:action="add"><Order>25</Order><CommandLine>reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v shutdownwithoutlogon /t REG_DWORD /d 1 /f</CommandLine></SynchronousCommand>
103+
<SynchronousCommand wcm:action="add"><Order>26</Order><CommandLine>reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v shutdownwithoutlogon /t REG_DWORD /d 1 /f</CommandLine></SynchronousCommand>
99104
<!-- V10: Mark FirstLogonCommands completion -->
100-
<SynchronousCommand wcm:action="add"><Order>26</Order><CommandLine>cmd /c "echo %date% %time% > C:\install.success"</CommandLine></SynchronousCommand>
105+
<SynchronousCommand wcm:action="add"><Order>27</Order><CommandLine>cmd /c "echo %date% %time% > C:\install.success"</CommandLine></SynchronousCommand>
101106
</FirstLogonCommands>
102107
</component>
103108
</settings>

scripts/remediate.ps1

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,13 @@ if (-not $vgt) {
9090
}
9191

9292
# --- ACPI power button = Shut down ---
93-
powercfg /setacvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3
94-
powercfg /setdcvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3
93+
# On Win11 25H2 the PBUTTONACTION setting is hidden by default (Attributes=1),
94+
# so powercfg /setacvalueindex silently no-ops and verify.ps1's grep for
95+
# "0x00000003" in `powercfg /query ... PBUTTONACTION` output finds nothing.
96+
# Unhide the setting first, then set it.
97+
powercfg /attributes 4f971e89-eebd-4455-a8de-9e59040e7347 7648efa3-dd9c-4e3e-b566-50f929386280 -ATTRIB_HIDE
98+
powercfg /setacvalueindex SCHEME_CURRENT SUB_BUTTONS 7648efa3-dd9c-4e3e-b566-50f929386280 3
99+
powercfg /setdcvalueindex SCHEME_CURRENT SUB_BUTTONS 7648efa3-dd9c-4e3e-b566-50f929386280 3
95100
powercfg /setactive SCHEME_CURRENT
96101

97102
# --- Shutdown optimization ---

0 commit comments

Comments
 (0)