feat(prepare-win): stream toolchain/WU output to the console live

pypsrp's execute_ps is fully blocking, so Install-CIToolchain / Windows
Update output only appeared at the end behind a "still running (Ns)"
heartbeat. Add WinRmTransport.run_streaming: drive the script via pypsrp's
incremental begin_invoke/poll_invoke, emitting output, Write-Host
(information) and error stream lines as they arrive over the same WinRM
channel.

_run_with_heartbeat now prefers run_streaming (suppressing the CI_EXITCODE
marker line) and falls back to the blocking heartbeat only on an unexpected
pypsrp error — TransportError still propagates so the 3010 reboot handling
is unaffected.
This commit is contained in:
2026-06-09 21:00:18 +02:00
parent 107bff8c8a
commit e0b1c1ddcc
4 changed files with 181 additions and 4 deletions
+18 -4
View File
@@ -367,12 +367,26 @@ def _run_with_heartbeat(
check: bool = True,
heartbeat: int = 30,
) -> WinRmResult:
"""Run *script* via *transport* and print a heartbeat line every *heartbeat* s.
"""Run *script* and show its output live; fall back to a heartbeat line.
pypsrp's execute_ps is fully blocking — no streaming. The heartbeat
thread reassures the operator that the command is still running during
long-running steps (e.g. Windows Update, which can take 20-40 min).
Prefers :meth:`WinRmTransport.run_streaming` so long steps (toolchain
install, Windows Update — 20-40 min) print progress as it happens. The
``CI_EXITCODE:`` marker line is suppressed from the live echo. If streaming
is unavailable/fails, fall back to the blocking run + periodic heartbeat.
"""
def _echo_line(line: str) -> None:
if not line.strip().startswith("CI_EXITCODE:"):
click.echo(line)
try:
return transport.run_streaming(script, _echo_line, check=check)
except TransportError:
# Connection/command errors must propagate — e.g. the VM rebooting
# mid-update is caught upstream and treated as exit 3010.
raise
except Exception as exc: # pypsrp API quirk — degrade to the blocking path
click.echo(f" [WARN] live streaming failed ({exc}); using heartbeat.", err=True)
result_holder: list[WinRmResult] = []
exc_holder: list[BaseException] = []
stop_event = threading.Event()