feat(prepare-win): reboot to cmd shell before toolchain, only if needed

Up front, read Winlogon\Shell; if it isn't already cmd.exe, set it and do a
clean reboot before running the toolchain — so the shell is genuinely cmd
(not a killed-explorer half-state) for the whole servicing run. Idempotent:
when the shell is already cmd (re-runs), skip the reboot. Replaces the
earlier kill-explorer approach.
This commit is contained in:
2026-06-09 22:54:43 +02:00
parent 9c0758ceb7
commit 054bd42ff0
2 changed files with 24 additions and 10 deletions
+22 -10
View File
@@ -668,18 +668,30 @@ def template_prepare_win(
click.echo("[prepare-win] WinRM ready.")
try:
# Switch the interactive shell to cmd.exe up front, before the toolchain
# runs, so every reboot in the Windows Update 3010 loop already boots
# without explorer — no shell crash-loop during heavy servicing. Drop the
# live explorer now too (Winlogon relaunches the shell = cmd). Re-affirmed
# before the final reboot and baked into post-install for redundancy.
click.echo("[prepare-win] Setting interactive shell to cmd.exe ...")
transport.run(
"Set-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT"
"\\CurrentVersion\\Winlogon' Shell 'cmd.exe' -Type String -Force\n"
"Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue\n",
# Ensure the interactive shell is cmd.exe — via a clean reboot — before
# the toolchain runs, so explorer never runs during the heavy servicing
# (Windows Update 3010 loop) that would otherwise crash-loop it. Reboot
# only when the shell wasn't already cmd, so re-runs don't reboot need-
# lessly. Re-affirmed before the final reboot and baked into post-install.
shell_res = transport.run(
"(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT"
"\\CurrentVersion\\Winlogon').Shell",
check=False,
)
if "cmd.exe" not in shell_res.stdout.strip().lower():
click.echo(
f"[prepare-win] Shell is '{shell_res.stdout.strip() or '?'}' — "
"setting cmd.exe and rebooting ..."
)
transport.run(
"Set-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT"
"\\CurrentVersion\\Winlogon' Shell 'cmd.exe' -Type String -Force",
check=False,
)
_reboot_and_wait(transport, guest_ip, 5986, winrm_timeout)
click.echo("[prepare-win] Shell is now cmd.exe.")
else:
click.echo("[prepare-win] Shell already cmd.exe — no reboot needed.")
# Ensure C:\CI exists
transport.run(
+2
View File
@@ -434,6 +434,8 @@ def _make_transport() -> MagicMock:
def _run(script: str = "", **kwargs: object) -> WinRmResult:
if "PercentProcessorTime" in script: # pre-snapshot idle check
return WinRmResult(stdout='{"cpu":2.0,"ram":21.0,"top":"idle"}', stderr="", returncode=0)
if ").Shell" in script: # up-front shell check — already cmd, skip reboot
return WinRmResult(stdout="cmd.exe", stderr="", returncode=0)
if "ConvertTo-Json" in script:
return WinRmResult(stdout=val_json, stderr="", returncode=0)
return WinRmResult(stdout="CI_EXITCODE:0", stderr="", returncode=0)