diff --git a/src/ci_orchestrator/commands/template.py b/src/ci_orchestrator/commands/template.py index f10c43d..d4741d2 100644 --- a/src/ci_orchestrator/commands/template.py +++ b/src/ci_orchestrator/commands/template.py @@ -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( diff --git a/tests/python/test_commands_template.py b/tests/python/test_commands_template.py index 8ed93e7..9e3a627 100644 --- a/tests/python/test_commands_template.py +++ b/tests/python/test_commands_template.py @@ -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)