# Windows Template VM Setup Procedura di provisioning della template VM **Windows Server 2025** che alimenta i linked clone usati per ogni job CI. Il template viene creato **una volta**; lo snapshot `BaseClean` è la base immutabile per tutte le build successive. **Prerequisito**: aver eseguito [HOST-SETUP.md](HOST-SETUP.md) (`F:\CI\` esiste, credenziali archiviate, ISO Windows Server presente). --- ## Architettura: due script | Script | Dove gira | Scopo | | ---------------------------------------- | --------------- | -------------------------------------------------- | | `template/Prepare-TemplateSetup.ps1` | **Host** | Orchestratore: copia + esegue lo script in VM | | `template/Setup-TemplateVM.ps1` | **Dentro VM** | Provisioning effettivo (firewall, defender, tool) | `Prepare-TemplateSetup.ps1` apre una WinRM session, copia `Setup-TemplateVM.ps1` in `C:\CI\`, lo esegue, raccoglie l'exit code, valida lo stato del guest. --- ## Specifiche VM | Parametro | Valore | | ----------------------- | --------------------------------------------------------------- | | OS | Windows Server 2025 Standard/Datacenter (Core o Desktop OK) | | vCPU | 4 | | 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` | | ISO | Windows Server 2025 (da `F:\CI\ISO\`) | --- ## Fase A — Crea la VM e installa Windows ### A.1 Crea VM in VMware Workstation - File → New Virtual Machine → Custom - Hardware: 4 vCPU, 6144 MB, 80 GB thin - Network: **VMnet8 (NAT)** - VMX: `F:\CI\Templates\WinBuild\WinBuild.vmx` - CD/DVD: monta ISO Windows Server 2025 ### A.2 Installa Windows Server 2025 - Edition: Standard o Datacenter (Server Core o con Desktop) - Activation: vedi A.4 (deve essere fatto prima dello snapshot) ### A.3 Abilita WinRM dentro la VM Dentro la VM, come Administrator: ```cmd winrm quickconfig -q ``` ```powershell Enable-PSRemoting -Force -SkipNetworkProfileCheck Set-Item WSMan:\localhost\Service\Auth\Basic $true -Force Set-Item WSMan:\localhost\Service\AllowUnencrypted $true -Force Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force ``` ### A.4 Attiva Windows Server 2025 **IMPORTANTE**: deve essere fatto **prima** dello snapshot. I linked clone ereditano l'attivazione. ```cmd slmgr /dli ``` Se non attivato, scegli il metodo: - **KMS** (server in rete): `slmgr /skms ` → `slmgr /ato` - **MAK/Retail key**: `slmgr /ipk XXXXX-XXXXX-XXXXX-XXXXX-XXXXX` → `slmgr /ato` - **KMS pubblico** (lab): vedi https://github.com/robin113x/Windows-Server-Activation/ Verifica: `slmgr /xpr` — deve mostrare attivazione permanente o scadenza lontana. ### A.5 Annota IP DHCP Dentro la VM: ```cmd ipconfig ``` Annota l'IP `192.168.79.x` assegnato dal DHCP di VMnet8. --- ## Fase B — Provisioning automatico dall'host Dall'host (PowerShell elevato): ```powershell cd N:\Code\Workspace\Local-CI-CD-System\template # Provisioning completo + registrazione credenziali automatica: .\Prepare-TemplateSetup.ps1 -VMIPAddress 192.168.79.128 -StoreCredential # Se WU già applicato in precedenza: .\Prepare-TemplateSetup.ps1 -VMIPAddress 192.168.79.128 -SkipWindowsUpdate -StoreCredential ``` ### Cosa fa `Prepare-TemplateSetup.ps1` — passo per passo Ogni step ha validazione `Assert-Step` (throw on failure, `[OK]` su pass): 1. **Pre-flight**: script dir presente, `Setup-TemplateVM.ps1` presente, IP ottetti 0-255, TCP/5985 reachable 2. **Configura host WinRM client**: `AllowUnencrypted=true`, aggiunge `$VMIPAddress` ai `TrustedHosts` - Salva valori precedenti, ripristinati nel `finally` (host posture invariata) 3. **Validazione host WinRM**: `AllowUnencrypted=true`, `TrustedHosts` include IP 4. **Test connettività**: `Test-WSMan -Authentication Basic` con credenziali admin 5. **Apre PSSession** verso la VM 6. **Crea `C:\CI`** in guest, valida creazione 7. **Copia `Setup-TemplateVM.ps1`** in `C:\CI\Setup-TemplateVM.ps1`, valida size match 8. **Esegue `Setup-TemplateVM.ps1`** dentro la VM (vedi Fase C) 9. **Gestisce reboot 3010**: se WU richiede reboot, riavvia VM e suggerisce re-run con `-SkipWindowsUpdate` 10. **Post-setup remote validation** (9 check via singola `Invoke-Command`): - WinRM Running - User `$BuildUsername` exists + admin - UAC `EnableLUA=0` - Firewall tutti profili Off - .NET SDK installato - Python `C:\Python\python.exe` presente - MSBuild path presente - `C:\CI\{build,output,scripts}` presenti 11. **Restore host WinRM** nel `finally` (sempre eseguito, anche su failure) ### Parametri principali | Parametro | Default | Note | | --------------------- | -------------- | ----------------------------------------------------------------------------- | | `-VMIPAddress` | (mandatory) | IP DHCP della VM su VMnet8 | | `-AdminUsername` | Administrator | Account admin built-in nella VM | | `-AdminPassword` | (prompt) | Se vuoto, prompt SecureString | | `-BuildPassword` | (prompt) | Password per `ci_build` — mai hard-codare | | `-BuildUsername` | ci_build | Account CI dentro la VM | | `-DotNetSdkVersion` | 10.0 | Channel .NET SDK | | `-SkipWindowsUpdate` | (off) | Pass-through a Setup-TemplateVM.ps1 | | `-StoreCredential` | (off) | Registra `BuildUsername`/`BuildPassword` in Windows Credential Manager | | `-CredentialTarget` | BuildVMGuest | Target Credential Manager (deve corrispondere a quanto usato da Invoke-CIJob) | --- ## Fase C — Cosa fa `Setup-TemplateVM.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`. ``` 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 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 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) Step 9 VS Build Tools 2026 via Scheduled Task SYSTEM Step 10 Toolchain cross-check (dotnet, python, msbuild) Cleanup cleanmgr /sagerun:1 + DISM /StartComponentCleanup /ResetBase 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. - **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. - **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` | --- ## Fase D — Snapshot **Solo se `Prepare-TemplateSetup.ps1` exit 0** (tutti gli `Assert-Step` passati): 1. **Spegni la VM**: Start → Shut down (graceful, non hard-stop) 2. **Take snapshot** in VMware Workstation: - VM → Snapshot → Take Snapshot - **Nome esatto: `BaseClean`** (case-sensitive, usato da `New-BuildVM.ps1`) 3. **Lascia la VM spenta** — non modificarla mai più dopo lo snapshot 4. **Verifica `runner/config.yaml`**: `GITEA_CI_TEMPLATE_PATH=F:\CI\Templates\WinBuild\CI-WinBuild.vmx` --- ## Fase E — Verifica finale Dall'host: ```powershell # Test creazione clone manuale cd N:\Code\Workspace\Local-CI-CD-System\scripts .\New-BuildVM.ps1 -JobId 'smoke-test' # Verifica WinRM raggiungibile sul clone .\Wait-VMReady.ps1 -JobId 'smoke-test' -TimeoutSec 120 # Cleanup .\Remove-BuildVM.ps1 -JobId 'smoke-test' ``` Se i 3 step exit 0 → template pronto per produzione. --- ## Maintenance — refresh semestrale Il template va rinfrescato ogni ~180 giorni (KMS lease) o quando serve aggiornare la toolchain (.NET, VS Build Tools, Python). **Procedura**: 1. Power on della template VM (NIC: VMnet8 NAT) 2. Riattiva: `slmgr /ato` 3. Re-run `Prepare-TemplateSetup.ps1` per applicare update toolchain (passare flag opportuni) 4. Shut down + nuovo snapshot **versionato**: `BaseClean_` (TODO §2.5) 5. Aggiorna `GITEA_CI_SNAPSHOT_NAME` env var in `runner/config.yaml` 6. Tieni i 2 snapshot più recenti, cancella vecchi dopo 1 settimana di uso pulito --- ## Troubleshooting | Sintomo | Diagnosi / Fix | | ------------------------------------------- | ----------------------------------------------------------------------- | | `Cannot reach $VMIPAddress via WinRM` | VM non avviata, WinRM non abilitato, IP errato — verifica con ipconfig | | `Setup-TemplateVM.ps1 exited with code 3010`| Reboot richiesto post-WU. Re-run con `-SkipWindowsUpdate` | | `[Defender] Validation failed: GPO key` | Defender riattivato da Windows Update — disabilita via gpedit + reboot | | `VS Build Tools installation timed out` | Network slow o cache corrotta. Re-run con cache pulita (`C:\ProgramData\Microsoft\VisualStudio\Packages`) | | `Python version match` fail | `$PythonVersion` ≠ versione installata. Aggiorna param o reinstalla | | Post-setup `MSBuild not found` | VS install fallita silenziosa. Check `C:\CI\vs_log.txt` | | Pre-snapshot Final check fail | Stato VM rotto — non prendere snapshot. Re-run o ricomincia da zero | --- ## Backlog correlato (TODO.md) - **§1.1 [P0]** Migrare WinRM da HTTP/Basic a HTTPS/5986 con cert self-signed nel template - **§1.3 [P0]** Pinning hash SHA256 degli installer (Python, dotnet-install.ps1, vs_buildtools.exe) - **§1.6 [P2]** Documentare threat model per Defender/Firewall/UAC tutti off - **§2.5 [P1]** Snapshot versionato `BaseClean_` (rollback in caso di refresh rotto) - **§2.6 [P2]** Backup automatico VMDK template pre-snapshot - **In-VM Git Clone** — installare Git for Windows + 7-Zip nel template per ottimizzare flusso