diff --git a/TODO.md b/TODO.md index 73a34cf..2040c39 100644 --- a/TODO.md +++ b/TODO.md @@ -570,18 +570,18 @@ build più ampi senza toccare il template. Tool da aggiungere come step `Setup-WinBuild2025` (ognuno con `Assert-Step`): -| Tool | Versione | Razionale | -| ------------------- | ----------- | ------------------------------------------------------------ | -| **Git for Windows** | latest LTS | Self-clone in VM (alt a host-zip-transfer), submodule fetch | -| **7-Zip** | latest | Universale unpack/zip; molti installer e workflow ne dipendono | -| **PowerShell 7.x** | latest LTS | Più veloce di 5.1, parallel pipelines, operatori moderni | -| **NSIS** | latest | Test build installer (es. `nsis-plugin-nsinnounp`) | -| **CMake** | latest | Build system cross-platform per progetti C/C++ nativi | -| **Node.js LTS** | latest LTS | Frontend/Electron pipelines, JS toolchain (npm) | -| **WiX Toolset** | v4 stable | MSI building da .NET projects | -| **gh CLI** | latest | Upload artifact a release Gitea/GitHub, comment PR | -| **Sysinternals** | sysinternals.com `live` | Diagnostica process/handle quando build flaky | -| **vcpkg** | latest | Package manager C++ per dipendenze native | +| Tool | Versione | Razionale | +| ------------------- | ----------------------- | -------------------------------------------------------------- | +| **Git for Windows** | latest LTS | Self-clone in VM (alt a host-zip-transfer), submodule fetch | +| **7-Zip** | latest | Universale unpack/zip; molti installer e workflow ne dipendono | +| **PowerShell 7.x** | latest LTS | Più veloce di 5.1, parallel pipelines, operatori moderni | +| **NSIS** | latest | Test build installer (es. `nsis-plugin-nsinnounp`) | +| **CMake** | latest | Build system cross-platform per progetti C/C++ nativi | +| **Node.js LTS** | latest LTS | Frontend/Electron pipelines, JS toolchain (npm) | +| **WiX Toolset** | v4 stable | MSI building da .NET projects | +| **gh CLI** | latest | Upload artifact a release Gitea/GitHub, comment PR | +| **Sysinternals** | sysinternals.com `live` | Diagnostica process/handle quando build flaky | +| **vcpkg** | latest | Package manager C++ per dipendenze native | **Approccio**: - Pinning hash SHA256 per ogni installer (vedi §1.3) @@ -619,27 +619,33 @@ duplicazioni; ogni concern ha **una sola** sorgente di verità. **Stato attuale (duplicati)**: UAC, Explorer LaunchTo, Server Manager off, DisableCAD, OOBE telemetry, WU services disable — entrambi gli script li toccano. -### 7.1 [P1] Spostare base OS hardening da Setup a Deploy +### 7.1 [P1] Spostare base OS hardening da Setup a Deploy ✅ codice fatto 2026-05-10 File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1), [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1). +**Implementato**: Firewall disable + WinRM MaxMemory/IdleTimeout/MaxProcesses spostati in +Deploy post-install.ps1. Aggiunti anche lock screen, SM GPO policy key, SM scheduled task +disable, DisableCAD Winlogon, OOBE non-policy key (mancanti vs Setup 5c). +Setup Step 1/3/4b/5b/5c convertiti a Assert-Step only — nessun Set. +Pending §7.4 per e2e validation su VM reale. + **Da MUOVERE Setup → Deploy**: -| Concern Setup | Step attuale Setup | Azione Deploy | -| ----------------------------- | ------------------ | ------------------------------------------- | -| Firewall off all profiles | Step 1 | Aggiungere `Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled False` in post-install (ora Deploy aggiunge solo regole RDP/ICMP) | -| WinRM tuning memoria/timeout | Step 3 (parziale) | Aggiungere `MaxMemoryPerShellMB=2048`, `IdleTimeOut=7200000`, `MaxProcessesPerShell=25` in post-install dopo Enable-PSRemoting | +| Concern Setup | Step attuale Setup | Azione Deploy | +| ---------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | +| Firewall off all profiles | Step 1 | Aggiungere `Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled False` in post-install (ora Deploy aggiunge solo regole RDP/ICMP) | +| WinRM tuning memoria/timeout | Step 3 (parziale) | Aggiungere `MaxMemoryPerShellMB=2048`, `IdleTimeOut=7200000`, `MaxProcessesPerShell=25` in post-install dopo Enable-PSRemoting | **Da CONVERTIRE in Setup a Assert-Step (validation-only)**: -| Step Setup | Diventa | -| ---------- | ------- | -| Step 1 Firewall | `Assert-Step` 3 profili Enabled=False (richiede prima la riga "Da MUOVERE" → Deploy: Set-NetFirewallProfile -Enabled False) | -| Step 2 Defender | Già validation-only (refactor 2026-05-09) | -| Step 3 WinRM tuning | `Assert-Step` MaxMemoryPerShellMB, IdleTimeOut, AllowUnencrypted, Basic | -| Step 4b UAC | `Assert-Step` EnableLUA=0, LocalAccountTokenFilterPolicy=1 | -| Step 5b Explorer LaunchTo | `Assert-Step` HKCU LaunchTo=1 | -| Step 5c Server Manager / DisableCAD / OOBE | `Assert-Step` HKLM machine policy + DisableCAD=1 + OOBE policy | +| Step Setup | Diventa | +| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | +| Step 1 Firewall | `Assert-Step` 3 profili Enabled=False (richiede prima la riga "Da MUOVERE" → Deploy: Set-NetFirewallProfile -Enabled False) | +| Step 2 Defender | Già validation-only (refactor 2026-05-09) | +| Step 3 WinRM tuning | `Assert-Step` MaxMemoryPerShellMB, IdleTimeOut, AllowUnencrypted, Basic | +| Step 4b UAC | `Assert-Step` EnableLUA=0, LocalAccountTokenFilterPolicy=1 | +| Step 5b Explorer LaunchTo | `Assert-Step` HKCU LaunchTo=1 | +| Step 5c Server Manager / DisableCAD / OOBE | `Assert-Step` HKLM machine policy + DisableCAD=1 + OOBE policy | **Da MANTENERE in Setup (build env, non base OS)**: - Step 4: build user `ci_build` + admin @@ -653,10 +659,15 @@ File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1), - Setup run dopo Deploy: tutte Assert-Step pass `[OK]` immediato (no work) - Test `--Skip` orthogonali (no regressione) -### 7.2 [P1] WU lifecycle — strategia B (Deploy: GPO + Manual; Setup: lifecycle) +### 7.2 [P1] WU lifecycle — strategia B (Deploy: GPO + Manual; Setup: lifecycle) ✅ codice fatto 2026-05-10 File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1), [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1). +**Implementato**: Deploy sostituisce `sc.exe config start= disabled` con +`NoAutoUpdate=1` + `DisableWindowsUpdateAccess=1` GPO (servizi rimangono Manual). +Setup Step 6 Step A/D già gestisce reset + restart servizi (idempotente). +Pending §7.4 per e2e validation. + **Stato attuale (conflitto ordering)**: - Deploy: `sc.exe config wuauserv start= disabled` + `sc.exe stop wuauserv` (idem UsoSvc, WaaSMedicSvc) + GPO `NoAutoUpdate=1` - Setup Step 6: necessita servizi UP per SchTask SYSTEM run WU @@ -686,14 +697,10 @@ File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1), - Setup `-SkipWindowsUpdate`: Final gate vede `StartType=Disabled` (impostato da Step 6b) - Setup senza `-Skip`: WU runs, exit 0/2/3, poi servizi disabled -### 7.3 [P2] Update header docs di entrambi gli script -- [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1) `.DESCRIPTION`: - enumerare tutte le hardening voci che Deploy ora possiede (firewall, WinRM tuning, - WU GPO non disable) -- [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1) `.DESCRIPTION`: - marcare Step 1/3/4b/5b/5c come "validation-only — set by Deploy" -- [docs/WINDOWS-TEMPLATE-SETUP.md](docs/WINDOWS-TEMPLATE-SETUP.md): aggiornare descrizione - "Cosa fa Setup-WinBuild2025" — riflettere nuovo confine +### 7.3 [P2] Update header docs di entrambi gli script ✅ fatto 2026-05-10 +- [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1) `.DESCRIPTION`: aggiornato con tutte le hardening voci (firewall, WinRM tuning, WU GPO strategy B, lock screen, SM, OOBE). +- [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1) `.DESCRIPTION`: Steps 1/3/4b/5b/5c marcati "validation-only — set by Deploy"; step ordering rationale aggiornato. +- [docs/WINDOWS-TEMPLATE-SETUP.md](docs/WINDOWS-TEMPLATE-SETUP.md): architettura "tre script", VMX path corretti, Fase C step list + razionale + tabella validazioni aggiornati. ### 7.4 [P2] Test e2e refactor - Run Deploy stand-alone su VM test → verifica tutte le hardening voci presenti diff --git a/docs/WINDOWS-TEMPLATE-SETUP.md b/docs/WINDOWS-TEMPLATE-SETUP.md index 04f586a..492d0b1 100644 --- a/docs/WINDOWS-TEMPLATE-SETUP.md +++ b/docs/WINDOWS-TEMPLATE-SETUP.md @@ -9,12 +9,17 @@ credenziali archiviate, ISO Windows Server presente). --- -## Architettura: due script +## Architettura: tre script -| Script | Dove gira | Scopo | -| ---------------------------------------- | --------------- | -------------------------------------------------- | -| `template/Prepare-WinBuild2025.ps1` | **Host** | Orchestratore: copia + esegue lo script in VM | -| `template/Setup-WinBuild2025.ps1` | **Dentro VM** | Provisioning effettivo (firewall, defender, tool) | +| Script | Dove gira | Scopo | +| ---------------------------------------- | --------------- | ------------------------------------------------------------------- | +| `template/Deploy-WinBuild2025.ps1` | **Host** | Opzionale: crea VM e installa Windows unattended da ISO | +| `template/Prepare-WinBuild2025.ps1` | **Host** | Orchestratore: copia + esegue Setup nel guest via WinRM | +| `template/Setup-WinBuild2025.ps1` | **Dentro VM** | CI toolchain (user, .NET, Python, VS) + validazione hardening Deploy | + +**Confine Deploy / Setup** (refactor §7, 2026-05-10): +- `Deploy-WinBuild2025.ps1` si occupa dell'**OS hardening** (Firewall, WinRM, UAC, Defender, UX tweaks, WU GPO locks). Se usato, Setup valida questi come Assert-Step senza re-applicarli. +- `Setup-WinBuild2025.ps1` si occupa della **CI customization** (utente `ci_build`, cartelle `C:\CI`, toolchain .NET/Python/VS, Windows Update lifecycle, cleanup, final gate). `Prepare-WinBuild2025.ps1` apre una WinRM session, copia `Setup-WinBuild2025.ps1` in `C:\CI\`, lo esegue, raccoglie l'exit code, valida lo stato del guest. @@ -30,7 +35,7 @@ credenziali archiviate, ISO Windows Server presente). | RAM | 6144 MB (6 GB) | | Disco | 80 GB thin provisioned | | NIC | **VMnet8 (NAT)** — internet richiesto per WU + installer | -| VMX path | `F:\CI\Templates\WinBuild\WinBuild.vmx` | +| VMX path | `F:\CI\Templates\WinBuild\CI-WinBuild.vmx` | | ISO | Windows Server 2025 (da `F:\CI\ISO\`) | --- @@ -41,7 +46,7 @@ credenziali archiviate, ISO Windows Server presente). - File → New Virtual Machine → Custom - Hardware: 4 vCPU, 6144 MB, 80 GB thin - Network: **VMnet8 (NAT)** -- VMX: `F:\CI\Templates\WinBuild\WinBuild.vmx` +- VMX: `F:\CI\Templates\WinBuild\CI-WinBuild.vmx` - CD/DVD: monta ISO Windows Server 2025 ### A.2 Installa Windows Server 2025 @@ -142,21 +147,22 @@ Ogni step ha validazione `Assert-Step` (throw on failure, `[OK]` su pass): ## Fase C — Cosa fa `Setup-WinBuild2025.ps1` (dentro la VM) -Eseguito automaticamente da Prepare in Fase B. Ordine ottimizzato per ridurre overhead: -tutti i tweak UI/registro sono raggruppati **prima** di Windows Update (operazione lenta). -Ogni step ha validazione `Assert-Step`. +Eseguito automaticamente da Prepare in Fase B. Steps 1/2/3/4b/5b/5c sono +**validation-only** — verificano che Deploy abbia già applicato l'hardening OS. +Steps 4/5/6/6b/7-10 eseguono la CI customization effettiva. +Ogni step ha validazione `Assert-Step` (throw on failure). ``` -Step 1 Firewall off (Domain/Private/Public) ── primo, rimuove blocchi -Step 2 Defender + Antimalware off (Set-MpPreference + GPO) - ── prima di WinRM e WU -Step 3 WinRM hardening (Basic, AllowUnencrypted, 2 GB shell mem, 2h timeout) -Step 4 Build user account (ci_build, admin, no expire) ── prima di WU -Step 4b UAC off (EnableLUA=0, LocalAccountTokenFilterPolicy=1) +Step 1 Firewall: validation only — Deploy disabled all profiles [Assert-Step] +Step 2 Defender: validation only — Deploy set DisableAntiSpyware GPO + RTP off [Assert-Step] +Step 3 WinRM: validation only — Deploy set Basic/Unencrypted/MaxMem/Timeout [Assert-Step] +Step 4 Build user account (ci_build, admin, no expire) ── crea utente CI +Step 4b UAC: validation only — Deploy set EnableLUA=0 + TokenFilterPolicy [Assert-Step] Step 5 CI dirs: C:\CI\{build, output, scripts} ── C:\CI deve esistere prima di WU -Step 5b Explorer LaunchTo=1 (This PC) -Step 5c CI UX hardening: Server Manager off, DisableCAD, OOBE/telemetry off -Step 6 Windows Update via Scheduled Task SYSTEM ── senza scan Defender, C:\CI presente +Step 5b Explorer LaunchTo=1: validation only — Deploy set HKCU + Default hive [Assert-Step] +Step 5c CI UX hardening: validation only — Deploy set lock screen, SM policy/task, + DisableCAD, OOBE, telemetry [Assert-Step] +Step 6 Windows Update via Scheduled Task SYSTEM ── clears Deploy GPO locks, runs WU Step 6b Windows Update permanently disabled (post-update lockdown) Step 7 .NET SDK install (channel via dotnet-install.ps1) Step 8 Python install (3.13.3, all-users, C:\Python) @@ -168,41 +174,36 @@ Final Pre-snapshot gate — 7 check cross-cutting + no installer leftover ### Razionale dell'ordine -- **Firewall first** — Windows blocca alcune chiamate WinRM finché non è classificato il - profilo di rete. Off subito → niente race condition durante Step 3. -- **Defender second** — disabilitato prima di WinRM e WU. WU scarica/installa update - senza scan (5-15 min risparmiati). Step 7-9 (download + install ~200-400 MB di - payload .NET/Python/VS) non vengono scansionati. -- **WinRM third** — già non interferito da firewall/defender. Hardening pulito. -- **User + UAC (4/4b)** — operazioni registro rapide; UAC off prima di dir/tool evita - prompt di elevazione; nessun motivo di aspettare WU. +- **Steps 1-3 (validazioni)** — verificano prima le precondizioni Deploy (firewall off, + Defender off, WinRM pronto) in modo che i failure emergano subito, non a metà toolchain. +- **User + UAC (4/4b)** — crea `ci_build`; valida UAC off (set da Deploy); UAC off prima + di dir/tool evita prompt di elevazione negli step 7-9. - **CI dirs prima di WU (Step 5)** — il worker WU scrive file temp in `C:\CI\` (`wu_worker.ps1`, `wu_result.txt`, ecc.); la directory deve esistere prima. -- **UI tweaks (5b/5c)** — tutti i tweak registro raggruppati prima del WU (lento); - scritture veloci, nessuna dipendenza da WU o toolchain. -- **WU sixth** — gira con tutto già off + `C:\CI` presente; condizioni ottimali. -- **WU disable subito dopo** — snapshot cattura WU permanentemente off. +- **UI tweaks (5b/5c)** — validazioni registro rapide; raggruppate prima di WU (lento). +- **WU (Step 6)** — clears i GPO lock di Deploy (Step A), avvia COM API via SYSTEM task, + poi Step 6b re-disabilita tutto → snapshot cattura WU permanentemente off. - **Toolchain (7-9) last** — WU completato, nessun scan su payload installer. ### Validazioni per step (sintesi) -| Step | Check chiave | -| ---- | ------------------------------------------------------------------------------- | -| 1 | `Get-NetFirewallProfile -Profile Domain/Private/Public → Enabled=False` | -| 2 | 6 GPO key + `Set-MpPreference DisableRealtimeMonitoring=true` + ExclusionPath | -| 3 | WinRM Running+Automatic, AllowUnencrypted=true, Auth/Basic=true, MaxMem≥2048 | -| 4 | User exists, enabled, PasswordNeverExpires, member of Administrators | -| 4b | EnableLUA=0, LocalAccountTokenFilterPolicy=1 | -| 5 | Ogni dir Test-Path -PathType Container | -| 5b | HKCU LaunchTo=1 | -| 5c | ServerManager machine policy DoNotOpenAtLogon=1, HKCU DoNotOpenServerManagerAtLogon=1, DisableCAD=1, OOBE DisablePrivacyExperience=1, AllowTelemetry=0 | -| 6 | ResultCode ∈ {0,2,3}, no reboot required (else exit 3010) | -| 6b | wuauserv StartType=Disabled, NoAutoUpdate=1, DisableWindowsUpdateAccess=1 | -| 7 | `dotnet --version` channel match, machine PATH contiene `C:\dotnet` | -| 8 | `C:\Python\python.exe` esiste, version match esatto, PATH contiene `C:\Python` | -| 9 | MSBuild.exe esiste, esegue, PATH, `\VC\` dir presente | -| 10 | dotnet+python+msbuild tutti risolvibili (throw, non più warn) | -| Final| WinRM, firewall, user+admin, UAC, WU disabled, tutti tool, no leftover installer in `C:\CI` | +| Step | Natura | Check chiave | +| ---- | ---------- | ------------------------------------------------------------------------------ | +| 1 | Assert | `Get-NetFirewallProfile -Profile Domain/Private/Public → Enabled=False` | +| 2 | Assert | GPO DisableAntiSpyware=1 (set da Deploy) | +| 3 | Assert | WinRM Running+Automatic, AllowUnencrypted=true, Auth/Basic=true, MaxMem≥2048 | +| 4 | Operativo | User exists, enabled, PasswordNeverExpires, member of Administrators | +| 4b | Assert | EnableLUA=0, LocalAccountTokenFilterPolicy=1 | +| 5 | Operativo | Ogni dir Test-Path -PathType Container | +| 5b | Assert | HKCU LaunchTo=1 | +| 5c | Assert | NoLockScreen=1, SM policy DoNotOpenAtLogon=1, SM task Disabled, DisableCAD=1, OOBE DisablePrivacyExperience=1, AllowTelemetry=0 | +| 6 | Operativo | ResultCode ∈ {0,2,3}, no reboot required (else exit 3010) | +| 6b | Operativo | wuauserv StartType=Disabled, NoAutoUpdate=1, DisableWindowsUpdateAccess=1 | +| 7 | Operativo | `dotnet --version` channel match, machine PATH contiene `C:\dotnet` | +| 8 | Operativo | `C:\Python\python.exe` esiste, version match esatto, PATH contiene `C:\Python` | +| 9 | Operativo | MSBuild.exe esiste, esegue, PATH, `\VC\` dir presente | +| 10 | Operativo | dotnet+python+msbuild tutti risolvibili (throw) | +| Final| Cross-cut | WinRM, firewall, user+admin, UAC, WU disabled, tutti tool, no leftover installer in `C:\CI` | --- diff --git a/template/Deploy-WinBuild2025.ps1 b/template/Deploy-WinBuild2025.ps1 index 05db9d4..2207b17 100644 --- a/template/Deploy-WinBuild2025.ps1 +++ b/template/Deploy-WinBuild2025.ps1 @@ -36,13 +36,19 @@ Hardening done by post-install.ps1 inside the guest: - UAC off (EnableLUA=0, LocalAccountTokenFilterPolicy=1) - - Defender real-time protection off + - Defender real-time protection off + DisableAntiSpyware=1 GPO - DiagTrack/Telemetry off - - Windows Update services disabled - - RDP enabled (no NLA, lab only) + firewall rule - - WinRM enabled HTTP 5985 + HTTPS 5986 (self-signed cert), Basic auth, - AllowUnencrypted, TrustedHosts=* + - Windows Update GPO locks (NoAutoUpdate=1, DisableWindowsUpdateAccess=1); + services remain Manual — Setup-WinBuild2025 Step 6b permanently disables + them after applying updates (§7.2 Strategy B) + - Windows Firewall all profiles disabled (VMnet8 NAT lab; Setup Step 1 validates) + - RDP enabled (no NLA, lab only) + firewall rules for RDP/ICMP/WinRM-HTTPS + - WinRM HTTP 5985 + HTTPS 5986 (self-signed cert), Basic auth, + AllowUnencrypted, TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25 - ICMPv4 inbound allowed + - Lock screen disabled, Server Manager (policy + HKLM + task + HKCU + Default hive), + DisableCAD (Policies\System + Winlogon), OOBE/telemetry suppressed; + Setup-WinBuild2025 Step 5b/5c validate all of these - VMware Tools installed (Complete) from Tools ISO - install_complete.flag dropped in C:\Windows\Temp @@ -506,15 +512,22 @@ sc.exe config DiagTrack start= disabled | Out-Null sc.exe stop DiagTrack | Out-Null L 'Telemetry disabled' -# ── Windows Update services disabled ────────────────────────────────────── -foreach (`$svc in 'wuauserv','UsoSvc','WaaSMedicSvc') { - sc.exe config `$svc start= disabled | Out-Null - sc.exe stop `$svc | Out-Null -} -reg add 'HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' /v NoAutoUpdate /t REG_DWORD /d 1 /f | Out-Null -L 'Windows Update disabled' +# ── Windows Update GPO locks — services left Manual (§7.2 Strategy B) ─── +# Services remain at Windows default (Manual/on-demand). Setup-WinBuild2025 +# Step 6 invokes the WU COM API via a SYSTEM scheduled task; it clears these +# GPO keys first (Step A), applies updates, then permanently disables services +# in Step 6b (post-update lockdown). These keys block background auto-start +# between Deploy and Setup without breaking the on-demand COM API. +reg add 'HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' /v DisableWindowsUpdateAccess /t REG_DWORD /d 1 /f | Out-Null +reg add 'HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' /v NoAutoUpdate /t REG_DWORD /d 1 /f | Out-Null +L 'Windows Update GPO locks set (services remain Manual; Setup Step 6b permanently disables)' -# ── Firewall: profiles stay default; allow RDP + ICMP + WinRM HTTPS ────── +# ── Firewall: all profiles disabled + allow RDP + ICMP + WinRM HTTPS ───── +# Disabling all profiles removes any block on subsequent WinRM config and WU +# downloads. This VM lives on VMnet8 NAT behind the VMware NAT gateway — no +# inbound exposure from outside the host. Setup-WinBuild2025 Step 1 validates. +Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled False +L 'Windows Firewall disabled on all profiles' reg add 'HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server' /v fDenyTSConnections /t REG_DWORD /d 0 /f | Out-Null reg add 'HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' /v UserAuthentication /t REG_DWORD /d 0 /f | Out-Null Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' -ErrorAction SilentlyContinue @@ -533,6 +546,11 @@ try { New-NetFirewallRule -DisplayName 'WinRM-HTTPS' -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Profile Any | Out-Null L 'WinRM HTTPS listener configured' } catch { L "WinRM HTTPS failed: `$(`$_.Exception.Message)" } +winrm set winrm/config/winrs '@{MaxProcessesPerShell="25"}' | Out-Null +Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048 -Force 3>`$null +Set-Item WSMan:\localhost\Shell\IdleTimeOut 7200000 -Force 3>`$null +Set-Service -Name WinRM -StartupType Automatic 3>`$null +L 'WinRM shell limits: MaxMemory=2048MB IdleTimeout=2h MaxProcesses=25; StartType=Automatic' # ── Server Manager / WAC popup / first-logon UX ────────────────────────── # Server Manager auto-start off — machine-wide + current Administrator + Default @@ -549,6 +567,18 @@ reg add 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' /v Disa # OOBE privacy experience suppressed reg add 'HKLM\SOFTWARE\Policies\Microsoft\Windows\OOBE' /v DisablePrivacyExperience /t REG_DWORD /d 1 /f | Out-Null L 'Server Manager + WAC + CAD + OOBE prompts disabled' +# Supplemental UX hardening validated by Setup-WinBuild2025 Step 5c: +# Lock screen: suppress at CI console sessions (no interactive user ever logs in) +reg add 'HKLM\SOFTWARE\Policies\Microsoft\Windows\Personalization' /v NoLockScreen /t REG_DWORD /d 1 /f | Out-Null +# Server Manager GPO policy key (authoritative path on WS2025; HKLM key above is not always honoured) +reg add 'HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\ServerManager' /v DoNotOpenAtLogon /t REG_DWORD /d 1 /f | Out-Null +# Server Manager scheduled task — the actual launcher on WS2025; registry alone insufficient +schtasks /Change /TN '\Microsoft\Windows\Server Manager\ServerManager' /Disable 2>&1 | Out-Null +# DisableCAD in Winlogon (belt-and-suspenders with Policies\System key set above) +reg add 'HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' /v DisableCAD /t REG_DWORD /d 1 /f | Out-Null +# OOBE non-policy key (supplements the Policies\Windows\OOBE key set above) +reg add 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' /v DisablePrivacyExperience /t REG_DWORD /d 1 /f | Out-Null +L 'Lock screen, SM GPO policy + task, CAD Winlogon, OOBE non-policy key set' # ── IE Enhanced Security Configuration off (Admin) ─────────────────────── # Long-known CI papercut: ESC blocks every URL in admin sessions. diff --git a/template/Setup-WinBuild2025.ps1 b/template/Setup-WinBuild2025.ps1 index 4dd7949..312dc91 100644 --- a/template/Setup-WinBuild2025.ps1 +++ b/template/Setup-WinBuild2025.ps1 @@ -8,40 +8,40 @@ This script is run ONE TIME inside the template VM before taking the "BaseClean" snapshot. After the snapshot is taken, never modify the VM. - Installs and configures (each step followed by Assert-Step validation): - Step 1 — Firewall: all profiles disabled first — removes any block on subsequent steps + Prerequisite: Deploy-WinBuild2025.ps1 must have completed first (unattended OS + install + post-install.ps1 hardening). Steps 1, 3, 4b, 5b, 5c are validation-only + — they assert that Deploy already set the expected state rather than re-applying it. + Steps 4, 5, 6/6b, 7-10 perform actual CI customisation. + + Steps and Assert-Step validation: + Step 1 — Firewall: validation only — Deploy disabled all profiles. Validation: Domain/Private/Public all Enabled=False - Step 2 — Defender state validation only (disabled by Deploy-WinBuild2025 - during unattended install via DisableAntiSpyware GPO + RTP off). - Validation: GPO DisableAntiSpyware=1. No exclusion paths — with - Defender off the scanner isn't running, so exclusions are moot. - Step 3 — WinRM: Basic auth, AllowUnencrypted, MaxMemoryPerShellMB=2048, 2h timeout - Firewall + Defender already off — no interference during config + Step 2 — Defender: validation only — Deploy set DisableAntiSpyware=1 GPO + RTP off. + Validation: GPO DisableAntiSpyware=1 + Step 3 — WinRM: validation only — Deploy ran Enable-PSRemoting, Basic auth, + AllowUnencrypted, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25. Validation: service Running+Automatic, AllowUnencrypted, Auth/Basic, memory Step 4 — Local build user account ($BuildUsername, PasswordNeverExpires, Administrators) Validation: user exists, enabled, PasswordNeverExpires, admin membership - Step 4b — UAC disabled (EnableLUA=0, LocalAccountTokenFilterPolicy=1) + Step 4b — UAC: validation only — Deploy set EnableLUA=0, LocalAccountTokenFilterPolicy=1. Validation: both registry values verified Step 5 — CI working directories: C:\CI\build, C:\CI\output, C:\CI\scripts Must run before Windows Update — WU worker writes temp files to C:\CI Validation: each path exists as Container - Step 5b — Explorer LaunchTo=1 (This PC) for current user + Default profile hive + Step 5b — Explorer LaunchTo=1: validation only — Deploy set HKCU + Default hive. Validation: HKCU LaunchTo=1 - Step 5c — CI UX hardening (all UI tweaks before the long-running WU step): - Server Manager auto-start off (machine policy + HKCU Administrator + - Default User hive → ci_build inherits at first logon) - Ctrl+Alt+Del at login disabled (DisableCAD=1) — CI VMs never need it - OOBE privacy/telemetry prompt suppressed (DisablePrivacyExperience=1, - AllowTelemetry=0) — prevents first-logon interactive prompt in clones - Validation: machine policy key, HKCU key, DisableCAD, OOBE policy, telemetry - Step 6 — Windows Update via Scheduled Task as SYSTEM (avoids WinRM token limit) - Runs with Firewall + Defender off, C:\CI present — faster, no scan overhead + Step 5c — CI UX hardening: validation only — Deploy set lock screen, Server Manager + (policy + HKLM + task + HKCU + Default hive), DisableCAD (Policies\System + + Winlogon), OOBE/telemetry suppressed. + Validation: NoLockScreen, SM policy, SM task, DisableCAD, OOBE, telemetry + Step 6 — Windows Update via Scheduled Task as SYSTEM (avoids WinRM token limit). + Clears Deploy's GPO locks (Step A), starts services (Step D), runs COM API, + reports result code. Skipped with -SkipWindowsUpdate. Validation: result code 0/2/3, no reboot required (else exit 3010) - Step 6b — Windows Update permanently disabled (post-update lockdown) + Step 6b — Windows Update permanently disabled (post-update lockdown — always runs). wuauserv + UsoSvc set to Disabled; GPO keys NoAutoUpdate=1, - DisableWindowsUpdateAccess=1; WU scheduled tasks disabled - Runs unconditionally (even with -SkipWindowsUpdate) so snapshot - captures WU off — linked clones never trigger background updates + DisableWindowsUpdateAccess=1; WU scheduled tasks disabled. + Snapshot captures WU permanently off — linked clones never auto-update. Validation: wuauserv StartType=Disabled, GPO keys present Step 7 — .NET SDK (channel $DotNetSdkVersion) via dotnet-install.ps1 Validation: dotnet resolvable, version channel match, machine PATH @@ -50,28 +50,27 @@ Step 9 — Visual Studio Build Tools 2026 via Scheduled Task as SYSTEM Validation: MSBuild.exe exists, executes, PATH, VC dir present Step 10 — Toolchain cross-check (dotnet, python, msbuild) - Throws on any missing tool (was: warn-only) + Throws on any missing tool Cleanup — Disk cleanup: cleanmgr, SoftwareDistribution, CBS, TEMP, Prefetch, DISM wuauserv already Disabled (Step 6b) — not restarted after cache clear - SoftwareDistribution\Download cleared best-effort (WaaSMedicSvc may repopulate) - Validation: wuauserv StartType Disabled (SoftwareDistribution check removed — - WaaSMedicSvc tamper-protection restores files immediately) + Validation: wuauserv StartType Disabled Final — Pre-snapshot gate: 7 cross-cutting checks across all steps Throws if any check fails — prevents snapshot of broken state Step ordering rationale: - Firewall first — removes any network block before WinRM config and WU downloads - Defender second — eliminates scan overhead before WinRM, WU, and installer downloads - WinRM third — configured clean with no Firewall/Defender interference - User + UAC — quick registry ops; UAC off before dirs/tools avoids elevation prompts - CI dirs — C:\CI must exist before WU worker writes temp files there - UI tweaks — all fast registry writes batched before the slow WU step - WU sixth — all prereqs (dirs, user, tweaks) done; Firewall+Defender off - WU disable — immediately after WU completes; snapshot captures WU permanently off - Toolchain — .NET / Python / VS last; WU done, no scan on installer payloads + Firewall (Step 1) — validates Deploy disabled all profiles; assertion before WinRM/WU + Defender (Step 2) — validates Deploy disabled RTP; assertion before tool install + WinRM (Step 3) — validates Deploy configured WinRM; assertion before remote steps + User (Step 4) — creates ci_build; must exist before UAC and dirs assertions + UAC (Step 4b) — validates Deploy disabled UAC (elevation-free installs in 7-9) + CI dirs (Step 5) — C:\CI must exist before WU worker writes temp files there + UI tweaks (5b/5c) — validates Deploy UX settings; fast assertions before slow WU + WU (Step 6) — all prereqs validated; WU runs with Firewall+Defender already off + WU disable (Step 6b) — immediately after WU; snapshot captures WU permanently off + Toolchain (7-9) — .NET/Python/VS last; WU done, no scan on installer payloads - NOTE: Git is NOT installed. The host clones the repository and copies - the source tree into the VM via WinRM (Option B isolation model). + NOTE: Git is NOT installed by default. See §6.6 (Tier-1 Toolchain) in TODO.md + for the planned addition. The host clones and copies source via WinRM until then. ╔══════════════════════════════════════════════════════════════════════╗ ║ NETWORK REQUIREMENT DURING SETUP ║ @@ -173,16 +172,12 @@ foreach ($f in $knownTempFiles) { } } -# ── Step 1: Firewall ────────────────────────────────────────────────────────── -Write-Step "Disabling Windows Firewall (all profiles) — isolated lab VM" +# ── Step 1: Firewall (validation only — disabled by Deploy-WinBuild2025) ────── +Write-Step "Firewall state (validation only — disabled at deploy time)" -# Disable first: removes any block on subsequent WinRM config and WU downloads. -# This VM lives on VMnet8 NAT behind the VMware NAT gateway — no inbound exposure -# from outside the host, so full disable is acceptable. -Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled False -Write-Host "Windows Firewall disabled on all profiles." +# Deploy-WinBuild2025.ps1 post-install.ps1 called Set-NetFirewallProfile -Enabled False +# on all profiles. Validated here before WinRM and WU assertions proceed. -# Validation foreach ($p in 'Domain','Private','Public') { Assert-Step 'Firewall' "$p profile disabled" { (Get-NetFirewallProfile -Profile $p -ErrorAction Stop).Enabled -eq $false @@ -201,29 +196,13 @@ Assert-Step 'Defender' 'GPO DisableAntiSpyware=1 (set by Deploy-WinBuild2025)' { ((Get-ItemProperty -Path $key -Name DisableAntiSpyware -ErrorAction SilentlyContinue).DisableAntiSpyware -eq 1) } -# ── Step 3: WinRM ───────────────────────────────────────────────────────────── -Write-Step "Configuring WinRM" +# ── Step 3: WinRM (validation only — configured by Deploy-WinBuild2025) ─────── +Write-Step "WinRM state (validation only — configured at deploy time)" -# Firewall and Defender already off — no interference during WinRM hardening. -Enable-PSRemoting -Force -SkipNetworkProfileCheck 3>$null | Out-Null +# Deploy-WinBuild2025.ps1 post-install.ps1 ran Enable-PSRemoting, set Basic auth, +# AllowUnencrypted, TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25, +# and StartupType=Automatic. Validated here before the build user and toolchain steps. -# Allow unencrypted (HTTP/5985) — acceptable for an isolated lab network. -# Migrate to HTTPS/5986 with a self-signed cert for hardened environments. -winrm set winrm/config/service '@{AllowUnencrypted="true"}' | Out-Null -winrm set winrm/config/service/auth '@{Basic="true"}' | Out-Null - -# Increase shell memory and timeout limits for long builds -winrm set winrm/config/winrs '@{MaxProcessesPerShell="25"}' | Out-Null -Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048 -Force 3>$null -Set-Item WSMan:\localhost\Shell\IdleTimeOut 7200000 -Force 3>$null # 2 hours - -# Ensure WinRM starts automatically -Set-Service -Name WinRM -StartupType Automatic 3>$null -Start-Service WinRM 3>$null - -Write-Host "WinRM configured." - -# Validation Assert-Step 'WinRM' 'service Running' { (Get-Service WinRM -ErrorAction Stop).Status -eq 'Running' } @@ -294,19 +273,13 @@ Assert-Step 'User' "user '$BuildUsername' member of Administrators" { [bool](& net localgroup Administrators 2>&1 | Select-String -SimpleMatch $BuildUsername) } -# ── Step 4b: Disable UAC ───────────────────────────────────────────────────── -Write-Step "Disabling UAC (isolated lab VM)" +# ── Step 4b: UAC (validation only — disabled by Deploy-WinBuild2025) ───────── +Write-Step "UAC state (validation only — disabled at deploy time)" -# EnableLUA=0 disables UAC entirely — admin processes get full token without prompt. -# LocalAccountTokenFilterPolicy=1 ensures remote admin connections (WinRM) also -# get an elevated token (avoids the filtered-token issue with runProgramInGuest). -Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' ` - -Name EnableLUA -Value 0 -Type DWord -Force -Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' ` - -Name LocalAccountTokenFilterPolicy -Value 1 -Type DWord -Force -Write-Host "UAC disabled." +# Deploy-WinBuild2025.ps1 post-install.ps1 set EnableLUA=0 (no prompt for admin +# processes) and LocalAccountTokenFilterPolicy=1 (WinRM remote connections get full +# elevated token, required for runProgramInGuest and remote admin commands). -# Validation $uacPolicyKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' Assert-Step 'UAC' 'EnableLUA=0' { (Get-ItemProperty -Path $uacPolicyKey -Name EnableLUA -ErrorAction Stop).EnableLUA -eq 0 @@ -335,109 +308,32 @@ foreach ($dir in $ciDirs) { } } -# ── Step 5b: Explorer settings ─────────────────────────────────────────────── -Write-Step "Configuring Explorer defaults" +# ── Step 5b: Explorer settings (validation only — configured by Deploy-WinBuild2025) ── +Write-Step "Explorer defaults (validation only — configured at deploy time)" + +# Deploy-WinBuild2025.ps1 post-install.ps1 set LaunchTo=1 for HKCU (Administrator) +# and the Default User hive (so ci_build and any new user inherits at first logon). -# Open Explorer to 'This PC' (Devices and Drives) instead of Quick Access. -# LaunchTo=1 = This PC, LaunchTo=2 = Quick Access (default) $explorerAdvKey = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Set-ItemProperty -Path $explorerAdvKey -Name 'LaunchTo' -Value 1 -Type DWord -Force - -# Also apply to the Default User profile so ci_build and any new user inherits it. -$defaultHive = 'C:\Users\Default\NTUSER.DAT' -$mountName = 'TempDefaultUser' -if (Test-Path $defaultHive) { - reg load "HKU\$mountName" $defaultHive | Out-Null - $defKey = "Registry::HKU\$mountName\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" - if (-not (Test-Path $defKey)) { New-Item -Path $defKey -Force | Out-Null } - Set-ItemProperty -Path $defKey -Name 'LaunchTo' -Value 1 -Type DWord -Force - [gc]::Collect() - reg unload "HKU\$mountName" | Out-Null - Write-Host "Explorer: 'This PC' set for current user and default profile." -} -else { - Write-Host "Explorer: 'This PC' set for current user (default hive not found)." -} - -# Validation Assert-Step 'Explorer' "LaunchTo=1 (HKCU current user)" { (Get-ItemProperty -Path $explorerAdvKey -Name LaunchTo -ErrorAction Stop).LaunchTo -eq 1 } -# ── Step 5c: CI UX hardening ───────────────────────────────────────────────── -Write-Step "CI UX hardening (lock screen off, Server Manager off, no CAD, no OOBE)" +# ── Step 5c: CI UX hardening (validation only — configured by Deploy-WinBuild2025) ─ +Write-Step "CI UX hardening state (validation only — configured at deploy time)" -# All UI tweaks batched here — fast registry writes before the slow WU step. +# Deploy-WinBuild2025.ps1 post-install.ps1 set: lock screen (NoLockScreen=1), Server +# Manager (policy + HKLM + task + HKCU + Default hive), DisableCAD (Policies\System + +# Winlogon), OOBE DisablePrivacyExperience (policy + non-policy), AllowTelemetry=0. -# 1 — Lock screen: disable entirely (CI VMs never need a lock screen) -# NoLockScreen=1 suppresses the lock screen shown before login on console sessions. $personPolicyKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization' -if (-not (Test-Path $personPolicyKey)) { New-Item -Path $personPolicyKey -Force | Out-Null } -Set-ItemProperty -Path $personPolicyKey -Name 'NoLockScreen' -Value 1 -Type DWord -Force -Write-Host "Lock screen disabled." - -# 2 — Server Manager: do not auto-open at logon -# Three layers needed on WS2025: -# a) Machine policy key (GPO path — read by ServerManager policy check) -# b) HKLM non-policy key (read directly by ServerManager.exe on WS2025) -# c) Scheduled Task '\Microsoft\Windows\Server Manager\ServerManager' -# — this is the actual launcher; registry alone is insufficient on WS2025 -# d) HKCU + Default hive for per-user belt-and-suspenders -$smPolicyKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\ServerManager' -if (-not (Test-Path $smPolicyKey)) { New-Item -Path $smPolicyKey -Force | Out-Null } -Set-ItemProperty -Path $smPolicyKey -Name 'DoNotOpenAtLogon' -Value 1 -Type DWord -Force - -$smHKLMKey = 'HKLM:\SOFTWARE\Microsoft\ServerManager' -if (-not (Test-Path $smHKLMKey)) { New-Item -Path $smHKLMKey -Force | Out-Null } -Set-ItemProperty -Path $smHKLMKey -Name 'DoNotOpenServerManagerAtLogon' -Value 1 -Type DWord -Force - -Disable-ScheduledTask -TaskPath '\Microsoft\Windows\Server Manager\' ` - -TaskName 'ServerManager' -ErrorAction SilentlyContinue | Out-Null - -# Current session user (Administrator) -$smUserKey = 'HKCU:\SOFTWARE\Microsoft\ServerManager' -if (-not (Test-Path $smUserKey)) { New-Item -Path $smUserKey -Force | Out-Null } -Set-ItemProperty -Path $smUserKey -Name 'DoNotOpenServerManagerAtLogon' -Value 1 -Type DWord -Force - -# Default User hive — ci_build and any future account inherits at first logon -$smDefaultHive = 'C:\Users\Default\NTUSER.DAT' -$smMountName = 'TempDefaultUserSM' -if (Test-Path $smDefaultHive) { - reg load "HKU\$smMountName" $smDefaultHive | Out-Null - $smDefKey = "Registry::HKU\$smMountName\SOFTWARE\Microsoft\ServerManager" - if (-not (Test-Path $smDefKey)) { New-Item -Path $smDefKey -Force | Out-Null } - Set-ItemProperty -Path $smDefKey -Name 'DoNotOpenServerManagerAtLogon' -Value 1 -Type DWord -Force - [gc]::Collect() - reg unload "HKU\$smMountName" | Out-Null - Write-Host "Server Manager: policy + HKLM + task + Administrator HKCU + Default hive set." -} -else { - Write-Host "Server Manager: policy + HKLM + task + Administrator HKCU set (Default hive not found)." -} - -# 3 — Disable Ctrl+Alt+Del at interactive login -# Set in both locations: Winlogon (classic) + Policies\System (policy, authoritative on WS2025). -Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' ` - -Name 'DisableCAD' -Value 1 -Type DWord -Force +$smPolicyKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\ServerManager' +$smHKLMKey = 'HKLM:\SOFTWARE\Microsoft\ServerManager' +$smUserKey = 'HKCU:\SOFTWARE\Microsoft\ServerManager' $systemPolicyKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Set-ItemProperty -Path $systemPolicyKey -Name 'DisableCAD' -Value 1 -Type DWord -Force -Write-Host "Ctrl+Alt+Del at login disabled (Winlogon + Policies\System)." +$oobePolicyKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\OOBE' +$dcKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection' -# 4 — Suppress OOBE privacy/telemetry prompt -$oobePolicyKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\OOBE' -if (-not (Test-Path $oobePolicyKey)) { New-Item -Path $oobePolicyKey -Force | Out-Null } -Set-ItemProperty -Path $oobePolicyKey -Name 'DisablePrivacyExperience' -Value 1 -Type DWord -Force - -$oobeKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' -if (-not (Test-Path $oobeKey)) { New-Item -Path $oobeKey -Force | Out-Null } -Set-ItemProperty -Path $oobeKey -Name 'DisablePrivacyExperience' -Value 1 -Type DWord -Force - -$dcKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection' -if (-not (Test-Path $dcKey)) { New-Item -Path $dcKey -Force | Out-Null } -Set-ItemProperty -Path $dcKey -Name 'AllowTelemetry' -Value 0 -Type DWord -Force -Write-Host "OOBE privacy prompt and telemetry disabled." - -# Validation Assert-Step 'CIUX' 'lock screen disabled (NoLockScreen=1)' { (Get-ItemProperty -Path $personPolicyKey -Name NoLockScreen -ErrorAction Stop).NoLockScreen -eq 1 }