fix(template): make prepare-win robust without prior Deploy run
- WinRM MaxProcessesPerShell: apply 100 if below threshold before asserting - DesktopHeap SharedSection: apply 4096 KB registry value if below threshold before asserting - Replace cleanmgr (hangs in WinRM Session 0, no window station) with direct PowerShell cleanup for dumps, WER files and Recycle Bin; DISM already covers component store and WU cache - vcpkg git clone/checkout: wrap with EAP=Continue to suppress PS5.1 NativeCommandError on git stderr (gotcha #3 from AGENTS.md) - template.py: filter CI_EXITCODE:N marker from displayed output - ci-static-ip.ps1: switch from vmware-rpctool (removed in recent Tools) to vmtoolsd --cmd; use temp-file redirect to capture output from native cmd Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)..."
|
||||
|
||||
@@ -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)..."
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user