diff --git a/src/ci_orchestrator/commands/template.py b/src/ci_orchestrator/commands/template.py index fdd7c96..8d9a1f7 100644 --- a/src/ci_orchestrator/commands/template.py +++ b/src/ci_orchestrator/commands/template.py @@ -292,6 +292,13 @@ def _parse_exit_marker(stdout: str) -> int: return 0 +def _strip_exit_marker(stdout: str) -> str: + """Remove ``CI_EXITCODE:N`` lines from output before display.""" + return "\n".join( + line for line in stdout.splitlines() if not line.strip().startswith("CI_EXITCODE:") + ) + + def _wait_winrm(transport: WinRmTransport, timeout: int) -> None: """Poll *transport*.is_ready() until True or *timeout* seconds elapse.""" deadline = time.monotonic() + timeout @@ -532,8 +539,9 @@ def template_prepare_win( try: result = _run_with_heartbeat(transport, invoke, check=False) exit_code = _parse_exit_marker(result.stdout) - if result.stdout.strip(): - click.echo(result.stdout.strip()) + display = _strip_exit_marker(result.stdout).strip() + if display: + click.echo(display) if result.stderr.strip(): click.echo(result.stderr.strip(), err=True) except TransportError: @@ -658,8 +666,9 @@ Register-ScheduledTask -TaskName 'CI-StaticIp' ` ) cleanup_result = _run_with_heartbeat(transport, cleanup_invoke, check=False) cleanup_ec = _parse_exit_marker(cleanup_result.stdout) - if cleanup_result.stdout.strip(): - click.echo(cleanup_result.stdout.strip()) + display = _strip_exit_marker(cleanup_result.stdout).strip() + if display: + click.echo(display) if cleanup_result.stderr.strip(): click.echo(cleanup_result.stderr.strip(), err=True) if cleanup_ec != 0: diff --git a/template/Install-CIToolchain-WinBuild2022.ps1 b/template/Install-CIToolchain-WinBuild2022.ps1 index 9a47905..7b4ad36 100644 --- a/template/Install-CIToolchain-WinBuild2022.ps1 +++ b/template/Install-CIToolchain-WinBuild2022.ps1 @@ -239,43 +239,15 @@ function Assert-Hash { function Invoke-DiskCleanup { Write-Step "Cleaning up disk" - $cleanKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches' - $sageset = 1 - $cleanCategories = @( - 'Active Setup Temp Folders', - 'BranchCache', - 'Downloaded Program Files', - 'Internet Cache Files', - 'Memory Dump Files', - 'Old ChkDsk Files', - 'Previous Installations', - 'Recycle Bin', - 'Service Pack Cleanup', - 'Setup Log Files', - 'System error memory dump files', - 'System error minidump files', - 'Temporary Files', - 'Temporary Setup Files', - 'Thumbnail Cache', - 'Update Cleanup', - 'Upgrade Discarded Files', - 'Windows Defender', - 'Windows Error Reporting Archive Files', - 'Windows Error Reporting Files', - 'Windows Error Reporting Queue Files', - 'Windows Error Reporting Temp Files', - 'Windows ESD installation files', - 'Windows Upgrade Log Files' - ) - foreach ($cat in $cleanCategories) { - $keyPath = "$cleanKey\$cat" - if (Test-Path $keyPath) { - Set-ItemProperty -Path $keyPath -Name "StateFlags$('{0:D4}' -f $sageset)" -Value 2 -Type DWord -Force -ErrorAction SilentlyContinue - } - } - Write-Host "Running cleanmgr /sagerun:$sageset (may take a few minutes)..." - $proc = Start-Process -FilePath 'cleanmgr.exe' -ArgumentList "/sagerun:$sageset" -Wait -PassThru - Write-Host "cleanmgr exited (code $($proc.ExitCode))." + # cleanmgr.exe requires an interactive window station — hangs in WinRM Session 0. + # Replicate the relevant categories with direct PowerShell cleanup instead. + Write-Host "Clearing memory dumps..." + Remove-Item 'C:\Windows\Minidump\*' -Force -Recurse -ErrorAction SilentlyContinue + Remove-Item 'C:\Windows\memory.dmp' -Force -ErrorAction SilentlyContinue + Write-Host "Clearing Windows Error Reporting files..." + Remove-Item 'C:\ProgramData\Microsoft\Windows\WER\*' -Force -Recurse -ErrorAction SilentlyContinue + Write-Host "Clearing Recycle Bin..." + Clear-RecycleBin -DriveLetter C -Force -ErrorAction SilentlyContinue Write-Host "Stopping WU-adjacent services before cache clear..." foreach ($svc in 'wuauserv', 'UsoSvc', 'bits', 'dosvc', 'TrustedInstaller') { @@ -437,9 +409,18 @@ Assert-Step 'WinRM' 'Shell MaxMemoryPerShellMB >= 2048' { Assert-Step 'WinRM' 'Plugin Microsoft.PowerShell MaxMemoryPerShellMB >= 2048' { [int](Get-Item 'WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB').Value -ge 2048 } +if ([int](Get-Item WSMan:\localhost\Shell\MaxProcessesPerShell -ErrorAction SilentlyContinue).Value -lt 100) { + Set-Item WSMan:\localhost\Shell\MaxProcessesPerShell 100 -Force +} Assert-Step 'WinRM' 'MaxProcessesPerShell >= 100' { [int](Get-Item WSMan:\localhost\Shell\MaxProcessesPerShell).Value -ge 100 } +$_subsysKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems' +$_subsysCur = (Get-ItemProperty $_subsysKey -Name Windows).Windows +if (-not ($_subsysCur -match 'SharedSection=\d+,\d+,(\d+)') -or [int]$Matches[1] -lt 4096) { + $subsysNew = $_subsysCur -replace 'SharedSection=(\d+),(\d+),\d+', 'SharedSection=$1,$2,4096' + Set-ItemProperty $_subsysKey -Name Windows -Value $subsysNew +} Assert-Step 'DesktopHeap' 'SharedSection non-interactive >= 4096 KB' { $w = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems' -Name Windows).Windows if ($w -match 'SharedSection=\d+,\d+,(\d+)') { [int]$Matches[1] -ge 4096 } else { $false } @@ -1587,11 +1568,15 @@ if (Test-Path $vcpkgExe) { $cloneArgs += '--depth' $cloneArgs += '1' } + $savedEap = $ErrorActionPreference; $ErrorActionPreference = 'Continue' & $gitBin.Source @cloneArgs 2>&1 | Write-Host + $ErrorActionPreference = $savedEap if ($LASTEXITCODE -ne 0) { throw "vcpkg git clone failed (exit $LASTEXITCODE)." } if ($VcpkgRef -ne '') { Write-Host "Checking out vcpkg ref: $VcpkgRef" + $savedEap = $ErrorActionPreference; $ErrorActionPreference = 'Continue' & $gitBin.Source -C $vcpkgDir checkout $VcpkgRef 2>&1 | Write-Host + $ErrorActionPreference = $savedEap if ($LASTEXITCODE -ne 0) { throw "vcpkg checkout '$VcpkgRef' failed (exit $LASTEXITCODE)." } } Write-Host "Bootstrapping vcpkg (-disableMetrics)..." diff --git a/template/Install-CIToolchain-WinBuild2025.ps1 b/template/Install-CIToolchain-WinBuild2025.ps1 index 2f443f4..ab1edf7 100644 --- a/template/Install-CIToolchain-WinBuild2025.ps1 +++ b/template/Install-CIToolchain-WinBuild2025.ps1 @@ -239,47 +239,17 @@ function Assert-Hash { function Invoke-DiskCleanup { Write-Step "Cleaning up disk" - # 1. Windows Disk Cleanup (cleanmgr) — run with /sagerun:1 using preset flags - # First register the flags via /sageset:1 in the registry (silent, no UI) - $cleanKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches' - $sageset = 1 - $cleanCategories = @( - 'Active Setup Temp Folders', - 'BranchCache', - 'Downloaded Program Files', - 'Internet Cache Files', - 'Memory Dump Files', - 'Old ChkDsk Files', - 'Previous Installations', - 'Recycle Bin', - 'Service Pack Cleanup', - 'Setup Log Files', - 'System error memory dump files', - 'System error minidump files', - 'Temporary Files', - 'Temporary Setup Files', - 'Thumbnail Cache', - 'Update Cleanup', - 'Upgrade Discarded Files', - 'Windows Defender', - 'Windows Error Reporting Archive Files', - 'Windows Error Reporting Files', - 'Windows Error Reporting Queue Files', - 'Windows Error Reporting Temp Files', - 'Windows ESD installation files', - 'Windows Upgrade Log Files' - ) - foreach ($cat in $cleanCategories) { - $keyPath = "$cleanKey\$cat" - if (Test-Path $keyPath) { - Set-ItemProperty -Path $keyPath -Name "StateFlags$('{0:D4}' -f $sageset)" -Value 2 -Type DWord -Force -ErrorAction SilentlyContinue - } - } - Write-Host "Running cleanmgr /sagerun:$sageset (may take a few minutes)..." - $proc = Start-Process -FilePath 'cleanmgr.exe' -ArgumentList "/sagerun:$sageset" -Wait -PassThru - Write-Host "cleanmgr exited (code $($proc.ExitCode))." + # cleanmgr.exe requires an interactive window station — hangs in WinRM Session 0. + # Replicate the relevant categories with direct PowerShell cleanup instead. + Write-Host "Clearing memory dumps..." + Remove-Item 'C:\Windows\Minidump\*' -Force -Recurse -ErrorAction SilentlyContinue + Remove-Item 'C:\Windows\memory.dmp' -Force -ErrorAction SilentlyContinue + Write-Host "Clearing Windows Error Reporting files..." + Remove-Item 'C:\ProgramData\Microsoft\Windows\WER\*' -Force -Recurse -ErrorAction SilentlyContinue + Write-Host "Clearing Recycle Bin..." + Clear-RecycleBin -DriveLetter C -Force -ErrorAction SilentlyContinue - # 2. Clear Windows Update cache + # Clear Windows Update cache Write-Host "Stopping WU-adjacent services before cache clear..." foreach ($svc in 'wuauserv', 'UsoSvc', 'bits', 'dosvc', 'TrustedInstaller') { Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue @@ -451,9 +421,18 @@ Assert-Step 'WinRM' 'Shell MaxMemoryPerShellMB >= 2048' { Assert-Step 'WinRM' 'Plugin Microsoft.PowerShell MaxMemoryPerShellMB >= 2048' { [int](Get-Item 'WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB').Value -ge 2048 } +if ([int](Get-Item WSMan:\localhost\Shell\MaxProcessesPerShell -ErrorAction SilentlyContinue).Value -lt 100) { + Set-Item WSMan:\localhost\Shell\MaxProcessesPerShell 100 -Force +} Assert-Step 'WinRM' 'MaxProcessesPerShell >= 100' { [int](Get-Item WSMan:\localhost\Shell\MaxProcessesPerShell).Value -ge 100 } +$_subsysKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems' +$_subsysCur = (Get-ItemProperty $_subsysKey -Name Windows).Windows +if (-not ($_subsysCur -match 'SharedSection=\d+,\d+,(\d+)') -or [int]$Matches[1] -lt 4096) { + $subsysNew = $_subsysCur -replace 'SharedSection=(\d+),(\d+),\d+', 'SharedSection=$1,$2,4096' + Set-ItemProperty $_subsysKey -Name Windows -Value $subsysNew +} Assert-Step 'DesktopHeap' 'SharedSection non-interactive >= 4096 KB' { $w = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems' -Name Windows).Windows if ($w -match 'SharedSection=\d+,\d+,(\d+)') { [int]$Matches[1] -ge 4096 } else { $false } @@ -1601,11 +1580,15 @@ if (Test-Path $vcpkgExe) { $cloneArgs += '--depth' $cloneArgs += '1' } + $savedEap = $ErrorActionPreference; $ErrorActionPreference = 'Continue' & $gitBin.Source @cloneArgs 2>&1 | Write-Host + $ErrorActionPreference = $savedEap if ($LASTEXITCODE -ne 0) { throw "vcpkg git clone failed (exit $LASTEXITCODE)." } if ($VcpkgRef -ne '') { Write-Host "Checking out vcpkg ref: $VcpkgRef" + $savedEap = $ErrorActionPreference; $ErrorActionPreference = 'Continue' & $gitBin.Source -C $vcpkgDir checkout $VcpkgRef 2>&1 | Write-Host + $ErrorActionPreference = $savedEap if ($LASTEXITCODE -ne 0) { throw "vcpkg checkout '$VcpkgRef' failed (exit $LASTEXITCODE)." } } Write-Host "Bootstrapping vcpkg (-disableMetrics)..." diff --git a/template/guest-setup/windows/ci-static-ip.ps1 b/template/guest-setup/windows/ci-static-ip.ps1 index 9c1b948..f6a34c6 100644 --- a/template/guest-setup/windows/ci-static-ip.ps1 +++ b/template/guest-setup/windows/ci-static-ip.ps1 @@ -31,16 +31,24 @@ Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' -$vmtoolsRpc = 'C:\Program Files\VMware\VMware Tools\vmware-rpctool.exe' -if (-not (Test-Path $vmtoolsRpc -PathType Leaf)) { exit 0 } +# vmware-rpctool.exe was removed in recent VMware Tools versions; use vmtoolsd --cmd instead. +$vmtoolsd = 'C:\Program Files\VMware\VMware Tools\vmtoolsd.exe' +if (-not (Test-Path $vmtoolsd -PathType Leaf)) { exit 0 } function Read-GuestInfo { param([string]$Key) - $savedEap = $ErrorActionPreference; $ErrorActionPreference = 'Continue' - $out = & $vmtoolsRpc "info-get guestinfo.$Key" 2>&1 - $ErrorActionPreference = $savedEap - if ($LASTEXITCODE -eq 0 -and $out -notmatch 'No value found|^$') { - return ($out | Out-String).Trim() + # vmtoolsd writes to a file handle but not to a PS pipeline — use cmd.exe redirection. + $tmp = [System.IO.Path]::GetTempFileName() + try { + $savedEap = $ErrorActionPreference; $ErrorActionPreference = 'Continue' + cmd /c "`"$vmtoolsd`" --cmd `"info-get guestinfo.$Key`" > `"$tmp`" 2>&1" + $ErrorActionPreference = $savedEap + $text = (Get-Content $tmp -ErrorAction SilentlyContinue | Out-String).Trim() + if ($text -and $text -notmatch 'No value found|Error') { + return $text + } + } finally { + Remove-Item $tmp -ErrorAction SilentlyContinue } return '' } @@ -75,22 +83,22 @@ if (-not $adapter) { exit 1 } -# Disable DHCP and remove any existing IPv4 addresses. +# Disable DHCP and remove any existing IPv4 addresses and routes. $adapter | Set-NetIPInterface -Dhcp Disabled -ErrorAction SilentlyContinue +Get-NetRoute -InterfaceAlias $adapter.Name -AddressFamily IPv4 -ErrorAction SilentlyContinue | + Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue $adapter | Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | Remove-NetIPAddress -Confirm:$false -ErrorAction SilentlyContinue -# Apply static IP. -$ipParams = @{ - InterfaceAlias = $adapter.Name - IPAddress = $ip - PrefixLength = $prefix - ErrorAction = 'Stop' -} +# Apply static IP (no DefaultGateway here — add route separately to avoid conflicts). +New-NetIPAddress -InterfaceAlias $adapter.Name -IPAddress $ip ` + -PrefixLength $prefix -ErrorAction Stop | Out-Null + +# Add default route if gateway supplied. if ($gateway -match '^\d{1,3}(\.\d{1,3}){3}$') { - $ipParams['DefaultGateway'] = $gateway + New-NetRoute -InterfaceAlias $adapter.Name -DestinationPrefix '0.0.0.0/0' ` + -NextHop $gateway -ErrorAction SilentlyContinue | Out-Null } -New-NetIPAddress @ipParams | Out-Null # Use the gateway as DNS (fallback: Google DNS). $dns = @() @@ -101,7 +109,7 @@ Set-DnsClientServerAddress -InterfaceAlias $adapter.Name ` # Report IP back via guestinfo.ci-ip so the host also has it. $savedEap = $ErrorActionPreference; $ErrorActionPreference = 'Continue' -& $vmtoolsRpc "info-set guestinfo.ci-ip $ip" 2>&1 | Out-Null +& $vmtoolsd --cmd "info-set guestinfo.ci-ip $ip" 2>&1 | Out-Null $ErrorActionPreference = $savedEap exit 0