fix(templates): bump WinRM quotas + desktop heap for parallel Win builds
Lint / pssa (push) Failing after 3s
Lint / python (push) Failing after 3s

Parallel MSBuild fan-out (e.g. ns7zip x86/x64/x86-ansi) over pypsrp
Linux→Win fails with MSBuild MSB6003 / Win32 1816 'Not enough quota'
because two default guest limits are too tight for concurrent native
processes via WinRM:

  - Plugin Microsoft.PowerShell MaxMemoryPerShellMB = 150 MB
    (pypsrp uses the plugin endpoint, not the Shell endpoint that
    Deploy was previously bumping)
  - Non-interactive desktop heap SharedSection 3rd field = 768 KB
    (Session 0 csrss; CreateProcess fails when 3+ cl.exe spawn)

Pre-migration Win→Win went through Invoke-Command on PSSession with
quotas inherited from the local process, so the limits never bit.

Changes:
  - Deploy-WinBuild2025.ps1 / Deploy-WinBuild2022.ps1: also set the
    plugin-level MaxMemoryPerShellMB=2048, bump MaxProcessesPerShell
    25→100, patch SharedSection 3rd field 768→4096 (reboot needed for
    csrss to pick up, applied by post-install shutdown).
  - Install-CIToolchain-WinBuild2025/2022.ps1: add Assert-Step for
    plugin quota, MaxProcessesPerShell≥100, SharedSection≥4096; fail
    hard if any are under threshold.
  - docs/WINDOWS-TEMPLATE-SETUP.md: new "WinRM quotas e desktop heap"
    section explaining the diagnosis + one-shot patch for legacy
    templates; updated Step 3 validation row + troubleshooting entry.
  - AGENTS.md: error #13 with symptom, root cause, and pointer to docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 23:35:57 +02:00
parent 9688e7e2f8
commit a906f4696f
6 changed files with 122 additions and 11 deletions
+20 -2
View File
@@ -639,11 +639,29 @@ try {
New-NetFirewallRule -DisplayName 'WinRM-HTTPS' -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Profile Any -RemoteAddress '192.168.79.0/24' | Out-Null
L 'WinRM HTTPS listener configured (port 5986, restricted to 192.168.79.0/24)'
} catch { L "WinRM HTTPS failed: `$(`$_.Exception.Message)" }
winrm set winrm/config/winrs '@{MaxProcessesPerShell="25"}' | Out-Null
winrm set winrm/config/winrs '@{MaxProcessesPerShell="100"}' | Out-Null
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048 -Force 3>`$null
Set-Item WSMan:\localhost\Shell\IdleTimeOut 7200000 -Force 3>`$null
# Plugin-level quota: pypsrp (LinuxWin) uses Microsoft.PowerShell endpoint,
# which has its own MaxMemoryPerShellMB independent of the Shell quota above.
# Without this, parallel native processes (e.g. 3x cl.exe in MSBuild fan-out)
# fail with MSB6003 / Win32 1816 'Not enough quota'.
Set-Item 'WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB' 2048 -Force 3>`$null
Set-Service -Name WinRM -StartupType Automatic 3>`$null
L 'WinRM shell limits: MaxMemory=2048MB IdleTimeout=2h MaxProcesses=25; StartType=Automatic'
Restart-Service -Name WinRM -Force 3>`$null
L 'WinRM limits: Shell+Plugin MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=100; StartType=Automatic'
# Non-interactive desktop heap (csrss SharedSection)
# WinRM sessions run in Session 0 non-interactive window station; default
# desktop heap is 768 KB. CreateProcess fails with ERROR_NOT_ENOUGH_QUOTA
# (Win32 1816 MSB6003) when multiple compiler processes spawn in parallel.
# Bump the third SharedSection field to 4096 KB. Change is read by csrss only
# at boot the post-install shutdown at the end of this script applies it.
`$subsysKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems'
`$subsysCur = (Get-ItemProperty -Path `$subsysKey -Name Windows).Windows
`$subsysNew = `$subsysCur -replace 'SharedSection=(\d+),(\d+),\d+', 'SharedSection=`$1,`$2,4096'
Set-ItemProperty -Path `$subsysKey -Name Windows -Value `$subsysNew
L "Desktop heap (non-interactive) SharedSection 3rd field set to 4096 KB (takes effect after reboot)"
# Server Manager / WAC popup / first-logon UX
# Server Manager auto-start off machine-wide + current Administrator + Default
+20 -2
View File
@@ -639,11 +639,29 @@ try {
New-NetFirewallRule -DisplayName 'WinRM-HTTPS' -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Profile Any -RemoteAddress '192.168.79.0/24' | Out-Null
L 'WinRM HTTPS listener configured (port 5986, restricted to 192.168.79.0/24)'
} catch { L "WinRM HTTPS failed: `$(`$_.Exception.Message)" }
winrm set winrm/config/winrs '@{MaxProcessesPerShell="25"}' | Out-Null
winrm set winrm/config/winrs '@{MaxProcessesPerShell="100"}' | Out-Null
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048 -Force 3>`$null
Set-Item WSMan:\localhost\Shell\IdleTimeOut 7200000 -Force 3>`$null
# Plugin-level quota: pypsrp (LinuxWin) uses Microsoft.PowerShell endpoint,
# which has its own MaxMemoryPerShellMB independent of the Shell quota above.
# Without this, parallel native processes (e.g. 3x cl.exe in MSBuild fan-out)
# fail with MSB6003 / Win32 1816 'Not enough quota'.
Set-Item 'WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB' 2048 -Force 3>`$null
Set-Service -Name WinRM -StartupType Automatic 3>`$null
L 'WinRM shell limits: MaxMemory=2048MB IdleTimeout=2h MaxProcesses=25; StartType=Automatic'
Restart-Service -Name WinRM -Force 3>`$null
L 'WinRM limits: Shell+Plugin MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=100; StartType=Automatic'
# Non-interactive desktop heap (csrss SharedSection)
# WinRM sessions run in Session 0 non-interactive window station; default
# desktop heap is 768 KB. CreateProcess fails with ERROR_NOT_ENOUGH_QUOTA
# (Win32 1816 MSB6003) when multiple compiler processes spawn in parallel.
# Bump the third SharedSection field to 4096 KB. Change is read by csrss only
# at boot the post-install shutdown at the end of this script applies it.
`$subsysKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems'
`$subsysCur = (Get-ItemProperty -Path `$subsysKey -Name Windows).Windows
`$subsysNew = `$subsysCur -replace 'SharedSection=(\d+),(\d+),\d+', 'SharedSection=`$1,`$2,4096'
Set-ItemProperty -Path `$subsysKey -Name Windows -Value `$subsysNew
L "Desktop heap (non-interactive) SharedSection 3rd field set to 4096 KB (takes effect after reboot)"
# Server Manager / WAC popup / first-logon UX
# Server Manager auto-start off machine-wide + current Administrator + Default
+15 -3
View File
@@ -318,8 +318,10 @@ Assert-Step 'Defender' 'GPO DisableAntiSpyware=1 (set by Deploy-WinBuild2022)' {
Write-Step "WinRM state (validation only — configured at deploy time)"
# Deploy-WinBuild2022.ps1 post-install.ps1 ran Enable-PSRemoting, set Basic auth over HTTPS,
# AllowUnencrypted=false (HTTPS/5986 only), TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h,
# MaxProcesses=25, StartupType=Automatic. Validated here before the build user and toolchain steps.
# AllowUnencrypted=false (HTTPS/5986 only), TrustedHosts=*, Shell+Plugin MaxMemory=2048MB,
# IdleTimeout=2h, MaxProcesses=100, StartupType=Automatic, and bumped the non-interactive
# desktop heap (SharedSection 3rd field) to 4096 KB. Validated here before the build user
# and toolchain steps.
Assert-Step 'WinRM' 'service Running' {
(Get-Service WinRM -ErrorAction Stop).Status -eq 'Running'
@@ -333,9 +335,19 @@ Assert-Step 'WinRM' 'AllowUnencrypted=false (HTTPS-only)' {
Assert-Step 'WinRM' 'Auth/Basic=true' {
(Get-Item WSMan:\localhost\Service\Auth\Basic).Value -eq 'true'
}
Assert-Step 'WinRM' 'MaxMemoryPerShellMB >= 2048' {
Assert-Step 'WinRM' 'Shell MaxMemoryPerShellMB >= 2048' {
[int](Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB).Value -ge 2048
}
Assert-Step 'WinRM' 'Plugin Microsoft.PowerShell MaxMemoryPerShellMB >= 2048' {
[int](Get-Item 'WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB').Value -ge 2048
}
Assert-Step 'WinRM' 'MaxProcessesPerShell >= 100' {
[int](Get-Item WSMan:\localhost\Shell\MaxProcessesPerShell).Value -ge 100
}
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 }
}
# ── Step 3b: Windows KMS Activation ─────────────────────────────────────────
# Must run after network is confirmed (Step 3) and before Windows Update (Step 6)
+15 -3
View File
@@ -318,8 +318,10 @@ Assert-Step 'Defender' 'GPO DisableAntiSpyware=1 (set by Deploy-WinBuild2025)' {
Write-Step "WinRM state (validation only — configured at deploy time)"
# Deploy-WinBuild2025.ps1 post-install.ps1 ran Enable-PSRemoting, set Basic auth over HTTPS,
# AllowUnencrypted=false (HTTPS/5986 only), TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h,
# MaxProcesses=25, StartupType=Automatic. Validated here before the build user and toolchain steps.
# AllowUnencrypted=false (HTTPS/5986 only), TrustedHosts=*, Shell+Plugin MaxMemory=2048MB,
# IdleTimeout=2h, MaxProcesses=100, StartupType=Automatic, and bumped the non-interactive
# desktop heap (SharedSection 3rd field) to 4096 KB. Validated here before the build user
# and toolchain steps.
Assert-Step 'WinRM' 'service Running' {
(Get-Service WinRM -ErrorAction Stop).Status -eq 'Running'
@@ -333,9 +335,19 @@ Assert-Step 'WinRM' 'AllowUnencrypted=false (HTTPS-only)' {
Assert-Step 'WinRM' 'Auth/Basic=true' {
(Get-Item WSMan:\localhost\Service\Auth\Basic).Value -eq 'true'
}
Assert-Step 'WinRM' 'MaxMemoryPerShellMB >= 2048' {
Assert-Step 'WinRM' 'Shell MaxMemoryPerShellMB >= 2048' {
[int](Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB).Value -ge 2048
}
Assert-Step 'WinRM' 'Plugin Microsoft.PowerShell MaxMemoryPerShellMB >= 2048' {
[int](Get-Item 'WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB').Value -ge 2048
}
Assert-Step 'WinRM' 'MaxProcessesPerShell >= 100' {
[int](Get-Item WSMan:\localhost\Shell\MaxProcessesPerShell).Value -ge 100
}
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 }
}
# ── Step 3b: Windows KMS Activation ─────────────────────────────────────────
# Must run after network is confirmed (Step 3) and before Windows Update (Step 6)