fix(winrm-https): PS 5.1 Test-WSMan has no -SessionOption — remove from all call sites

Test-WSMan in Windows PowerShell 5.1 does not support -SessionOption (WSManSessionOption),
causing "Cannot find a parameter matching the name 'SessionOption'" at runtime.

Prepare-WinBuild2025.ps1:
  - Remove Test-WSMan connectivity check step entirely
  - Open New-PSSession directly (handles -SkipCACheck via PSSessionOption)
  - Wrap New-PSSession in try/catch with the same actionable error message
  - Remove $wsmOpt (WSManSessionOption) — no longer needed
  Wait-GuestWinRMReady: replace Test-WSMan probe with New-PSSession probe + Remove-PSSession

Wait-VMReady.ps1:
  - Remove $wsmOpt entirely
  - Replace Test-WSMan -UseSSL -SessionOption with Test-NetConnection -Port 5986
    (TCP open on 5986 = HTTPS listener up = VM ready; credentials not available here)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Simone
2026-05-10 15:58:57 +02:00
parent 68cde01c9d
commit 34b33c5011
3 changed files with 101 additions and 76 deletions
+70 -42
View File
@@ -16,7 +16,35 @@
> - **P3** — estensioni nice-to-have > - **P3** — estensioni nice-to-have
--- ---
## Summary
_Last updated: 2026-05-10 (post §7.4 e2e + tag v1.2)_
**Stato generale**: infrastruttura base e refactor Deploy↔Setup (§7) completi. Sicurezza sprint 1 in corso (HTTPS/5986 done, IP regex done, hash pinning struttura done — valori da riempire). Backlog operatività/perf/qualità intatto.
| Area | Done | Open | Note |
| ------------------------------- | ---: | ---: | ----------------------------------------------------------------- |
| Setup & Infrastructure | all | 0 | Gitea + runner + dirs + rete tutti operativi |
| Template VM (Fasi A→D) | all | 0 | Snapshot `BaseClean` preso, credenziali in Credential Manager |
| Scripts Verification | all | 0 | e2e-008/009 SUCCESS, cleanup orfani + retry deleteVM ok |
| Runner Configuration | all | 0 | `config.yaml`, capacity 4, `BuildVMGuest` |
| Gitea Workflow Integration | 4 | 1 | manca solo §P3 generalizzazione workflow |
| §1 Sicurezza & hardening | 2 | 6 | 1.1, 1.4 done; 1.2/1.3 parzialmente; 1.5/1.6/1.7/1.8 da fare |
| §2 Affidabilità & resilienza | 0 | 7 | tutto backlog (race IP, cleanup schedulato, snapshot versionato…) |
| §3 Performance & throughput | 0 | 6 | nessuna ottimizzazione applicata; baseline §3.6 prerequisito |
| §4 Osservabilità & manutenzione | 0 | 5 | log JSONL, metriche, runbook tutti aperti |
| §5 Qualità codice & test | 0 | 5 | Pester, modulo `_Common.psm1`, regole PSScriptAnalyzer custom |
| §6 Scalabilità & estensioni | 0 | 6 | Linux VM, composite action, toolchain Tier-1 |
| §7 Refactor Deploy ↔ Setup | 4 | 1 | 7.1/7.2/7.3/7.4 done; 7.5 (rinomina) opzionale |
| In-VM Git Clone (§3.3) | 0 | 4 | dipende da §6.6 (Git+7-Zip nel template) |
| Linux Build VM (§6.1) | 0 | 4 | bozza in `docs/LINUX-TEMPLATE-SETUP.md` |
**Prossimi passi consigliati** (vedi anche "Suggerimento di sequencing" in fondo):
1. Chiudere §1.3 — riempire hash SHA256 in `Setup-WinBuild2025.ps1` prima del prossimo refresh snapshot.
2. Audit §1.2 (TrustedHosts `*` su host), poi §1.7 (rotazione password guest).
3. Sprint 2 operatività — §2.2 (cleanup schedulato), §2.3 (retention), §2.5 (snapshot versionato).
---
## Setup & Infrastructure ## Setup & Infrastructure
- [x] **Gitea Server** — già in esecuzione su `http://10.10.20.11:3100` (e `https://gitea.emulab.it`) - [x] **Gitea Server** — già in esecuzione su `http://10.10.20.11:3100` (e `https://gitea.emulab.it`)
@@ -148,7 +176,7 @@
## 1. Sicurezza & hardening ## 1. Sicurezza & hardening
### 1.1 [P0] Migrare WinRM da HTTP/Basic a HTTPS/5986 — COMPLETATO 2026-05-10 ### 1.1 [P0] [x] Migrare WinRM da HTTP/Basic a HTTPS/5986 — COMPLETATO 2026-05-10
**Azioni**: **Azioni**:
- [x] Generare cert self-signed nel template *prima* dello snapshot (già in Deploy post-install.ps1). - [x] Generare cert self-signed nel template *prima* dello snapshot (già in Deploy post-install.ps1).
@@ -160,7 +188,7 @@
- [x] `Prepare-WinBuild2025.ps1`: preflight TCP/5986, host non imposta più `AllowUnencrypted`; solo `TrustedHosts` viene salvato/ripristinato. - [x] `Prepare-WinBuild2025.ps1`: preflight TCP/5986, host non imposta più `AllowUnencrypted`; solo `TrustedHosts` viene salvato/ripristinato.
- [x] `Validate-DeployState.ps1`, `Validate-SetupState.ps1`: connessione HTTPS/5986, check `AllowUnencrypted=false`. - [x] `Validate-DeployState.ps1`, `Validate-SetupState.ps1`: connessione HTTPS/5986, check `AllowUnencrypted=false`.
### 1.2 [P0] Restringere `TrustedHosts` lato host (audit-only — già coperto) ### 1.2 [P0] [ ] Restringere `TrustedHosts` lato host (audit-only — residuo: audit host pre-esistenti)
File: nessuno script imposta `*` sull'host. Stato corrente: File: nessuno script imposta `*` sull'host. Stato corrente:
- `Setup-Host.ps1` non tocca TrustedHosts (verificato 2026-05-10) - `Setup-Host.ps1` non tocca TrustedHosts (verificato 2026-05-10)
- [template/Prepare-WinBuild2025.ps1](template/Prepare-WinBuild2025.ps1) appende `$VMIPAddress` ai `TrustedHosts` e ripristina nel `finally` (post-§1.1: solo TrustedHosts, no AllowUnencrypted) - [template/Prepare-WinBuild2025.ps1](template/Prepare-WinBuild2025.ps1) appende `$VMIPAddress` ai `TrustedHosts` e ripristina nel `finally` (post-§1.1: solo TrustedHosts, no AllowUnencrypted)
@@ -172,7 +200,7 @@ sostituire con la subnet build:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '192.168.79.*' -Force -Concatenate Set-Item WSMan:\localhost\Client\TrustedHosts -Value '192.168.79.*' -Force -Concatenate
``` ```
### 1.3 [P0] Pinning hash SHA256 degli installer — STRUTTURA AGGIUNTA 2026-05-10 ### 1.3 [P0] [ ] Pinning hash SHA256 degli installer — STRUTTURA AGGIUNTA 2026-05-10 (valori da riempire)
`$script:Hashes` + `Assert-Hash` aggiunti a [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1). `$script:Hashes` + `Assert-Hash` aggiunti a [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1).
`Assert-Hash` viene chiamato dopo ogni download (dotnet-install.ps1, python installer, vs_buildtools.exe). `Assert-Hash` viene chiamato dopo ogni download (dotnet-install.ps1, python installer, vs_buildtools.exe).
@@ -188,14 +216,14 @@ Se l'hash è `''` viene emesso un warning ma l'esecuzione continua (non-breaking
``` ```
- [ ] Verificare che `Assert-Hash` throwi correttamente su hash errato (test con hash fake). - [ ] Verificare che `Assert-Hash` throwi correttamente su hash errato (test con hash fake).
### 1.4 [P1] Validazione IP per-ottetto — COMPLETATO 2026-05-10 ### 1.4 [P1] [x] Validazione IP per-ottetto — COMPLETATO 2026-05-10
Regex per-ottetto sostituita in tutti e 4 i file: Regex per-ottetto sostituita in tutti e 4 i file:
`'^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$'` `'^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$'`
Estrazione in `scripts/_Common.psm1` — vedi §5.2 (P2, backlog). Estrazione in `scripts/_Common.psm1` — vedi §5.2 (P2, backlog).
### 1.5 [P1] PAT mai persistito quando si abilita `-UseGitClone` ### 1.5 [P1] [ ] PAT mai persistito quando si abilita `-UseGitClone`
Requisiti di sicurezza per l'implementazione del clone in-VM. La sequenza operativa Requisiti di sicurezza per l'implementazione del clone in-VM. La sequenza operativa
(quando, dove, come) è dettagliata nella sezione "In-VM Git Clone (Ottimizzazione) — riferimento §3.3". (quando, dove, come) è dettagliata nella sezione "In-VM Git Clone (Ottimizzazione) — riferimento §3.3".
@@ -207,7 +235,7 @@ Vincoli da rispettare in qualsiasi implementazione di `-UseGitClone`:
`transcript.txt`, marcare la build come fallita e ruotare il PAT (regola di safety net, `transcript.txt`, marcare la build come fallita e ruotare il PAT (regola di safety net,
non sostituisce le precedenti). non sostituisce le precedenti).
### 1.6 [P2] Defender + Firewall + UAC tutti disattivati nel template — documentare il modello di minaccia ### 1.6 [P2] [ ] Defender + Firewall + UAC tutti disattivati nel template — documentare il modello di minaccia
File: [template/Deploy-WinBuild2025.ps1:485-551](template/Deploy-WinBuild2025.ps1) (UAC, ServerManager, DisableCAD, OOBE — set da Deploy), [template/Setup-WinBuild2025.ps1:176-336](template/Setup-WinBuild2025.ps1) (Firewall Step 1, Defender Step 2 validation-only post-refactor 2026-05-09, WinRM Step 3, User Step 4, UAC Step 4b, Dirs Step 5). File: [template/Deploy-WinBuild2025.ps1:485-551](template/Deploy-WinBuild2025.ps1) (UAC, ServerManager, DisableCAD, OOBE — set da Deploy), [template/Setup-WinBuild2025.ps1:176-336](template/Setup-WinBuild2025.ps1) (Firewall Step 1, Defender Step 2 validation-only post-refactor 2026-05-09, WinRM Step 3, User Step 4, UAC Step 4b, Dirs Step 5).
Stato attuale (post-refactor §7): Stato attuale (post-refactor §7):
@@ -226,11 +254,11 @@ Le scelte sono ragionevoli per un template lab efimero, ma vanno raccolte in un'
Senza questa nota, future modifiche possono accumulare ulteriori riduzioni di sicurezza Senza questa nota, future modifiche possono accumulare ulteriori riduzioni di sicurezza
senza tracciamento. senza tracciamento.
### 1.7 [P2] Rotazione password guest VM ### 1.7 [P2] [ ] Rotazione password guest VM
- [ ] Trimestrale. Aggiornare credenziale `BuildVMGuest` in Credential Manager + password - [ ] Trimestrale. Aggiornare credenziale `BuildVMGuest` in Credential Manager + password
effettiva nel template (richiede refresh snapshot — coordinare con §2.5). effettiva nel template (richiede refresh snapshot — coordinare con §2.5).
### 1.8 [P2] Verifica Get-StoredCredential ### 1.8 [P2] [ ] Verifica Get-StoredCredential
- [ ] Confermare che `Get-StoredCredential` funzioni correttamente in tutti gli script che - [ ] Confermare che `Get-StoredCredential` funzioni correttamente in tutti gli script che
lo usano (post-migrazione HTTPS). lo usano (post-migrazione HTTPS).
@@ -238,7 +266,7 @@ senza tracciamento.
## 2. Affidabilità & resilienza ## 2. Affidabilità & resilienza
### 2.1 [P0] Race su IP allocation con `capacity: 4` ### 2.1 [P0] [ ] Race su IP allocation con `capacity: 4`
File: [runner/config.yaml:17](runner/config.yaml), [scripts/Invoke-CIJob.ps1:244-253](scripts/Invoke-CIJob.ps1). File: [runner/config.yaml:17](runner/config.yaml), [scripts/Invoke-CIJob.ps1:244-253](scripts/Invoke-CIJob.ps1).
**Stato osservato**: race non riprodotta in e2e-008/009 (un job alla volta). `capacity: 4` **Stato osservato**: race non riprodotta in e2e-008/009 (un job alla volta). `capacity: 4`
@@ -259,7 +287,7 @@ rilevata in fase di Wait-VMReady — apparirà come "WinRM risponde ma è la VM
L'opzione A è la più portabile e si integra con il pre-warm pool (§3.4). L'opzione A è la più portabile e si integra con il pre-warm pool (§3.4).
### 2.2 [P1] Cleanup orfani schedulato ### 2.2 [P1] [ ] Cleanup orfani schedulato
File: [scripts/Cleanup-OrphanedBuildVMs.ps1](scripts/Cleanup-OrphanedBuildVMs.ps1) — script pronto, manca lo scheduling. File: [scripts/Cleanup-OrphanedBuildVMs.ps1](scripts/Cleanup-OrphanedBuildVMs.ps1) — script pronto, manca lo scheduling.
Estendere `Setup-Host.ps1` per registrare un Task Scheduler: Estendere `Setup-Host.ps1` per registrare un Task Scheduler:
@@ -273,7 +301,7 @@ Register-ScheduledTask -TaskName 'CI-CleanupOrphans' -Action $action -Trigger $t
Aggiungere anche un trigger `AtStartup` per gestire crash dell'host. Aggiungere anche un trigger `AtStartup` per gestire crash dell'host.
Suggerito: ogni 6 ore, `-MaxAgeHours 4`. Suggerito: ogni 6 ore, `-MaxAgeHours 4`.
### 2.3 [P1] Retention artifact + log automatica ### 2.3 [P1] [ ] Retention artifact + log automatica
File: [docs/OPTIMIZATION.md:160-181](docs/OPTIMIZATION.md) — la logica c'è solo come snippet, non eseguita. File: [docs/OPTIMIZATION.md:160-181](docs/OPTIMIZATION.md) — la logica c'è solo come snippet, non eseguita.
Stessa pattern di §2.2 ma su `F:\CI\Artifacts` con `-AddDays(-30)` e su `F:\CI\Logs` con la Stessa pattern di §2.2 ma su `F:\CI\Artifacts` con `-AddDays(-30)` e su `F:\CI\Logs` con la
@@ -281,13 +309,13 @@ finestra già configurata in `Invoke-CIJob.ps1`. Aggiungere
`Get-PSDrive F | Where-Object { $_.Free -lt 50GB }` come *guard*: se libero < 50 GB, `Get-PSDrive F | Where-Object { $_.Free -lt 50GB }` come *guard*: se libero < 50 GB,
retention più aggressiva (7 giorni) e log warning. retention più aggressiva (7 giorni) e log warning.
### 2.4 [P1] `Remove-BuildVM.ps1` — supportare `-WhatIf` ### 2.4 [P1] [ ] `Remove-BuildVM.ps1` — supportare `-WhatIf`
File: [scripts/Remove-BuildVM.ps1:28](scripts/Remove-BuildVM.ps1). File: [scripts/Remove-BuildVM.ps1:28](scripts/Remove-BuildVM.ps1).
Aggiungere `[CmdletBinding(SupportsShouldProcess)]` come fatto in `Cleanup-OrphanedBuildVMs.ps1`. Aggiungere `[CmdletBinding(SupportsShouldProcess)]` come fatto in `Cleanup-OrphanedBuildVMs.ps1`.
Permette debug e dry-run senza forkare un secondo script. Permette debug e dry-run senza forkare un secondo script.
### 2.5 [P1] Snapshot versionato `BaseClean_<yyyyMMdd>` ### 2.5 [P1] [ ] Snapshot versionato `BaseClean_<yyyyMMdd>`
File: [docs/BEST-PRACTICES.md:142-145](docs/BEST-PRACTICES.md), [scripts/Invoke-CIJob.ps1:100](scripts/Invoke-CIJob.ps1). File: [docs/BEST-PRACTICES.md:142-145](docs/BEST-PRACTICES.md), [scripts/Invoke-CIJob.ps1:100](scripts/Invoke-CIJob.ps1).
**Motivazione**: quando si refresha il template (KMS lease semestrale, update toolchain), il **Motivazione**: quando si refresha il template (KMS lease semestrale, update toolchain), il
@@ -303,13 +331,13 @@ e non c'è rollback.
Si lega al task: refresh semestrale snapshot template VM (KMS lease = 180 giorni). Si lega al task: refresh semestrale snapshot template VM (KMS lease = 180 giorni).
Boot template su VMnet8 (NAT) → `slmgr /ato` → nuovo snapshot. Boot template su VMnet8 (NAT) → `slmgr /ato` → nuovo snapshot.
### 2.6 [P2] Backup automatico VMDK template ### 2.6 [P2] [ ] Backup automatico VMDK template
File: [docs/BEST-PRACTICES.md:130-138](docs/BEST-PRACTICES.md) — solo come snippet manuale. File: [docs/BEST-PRACTICES.md:130-138](docs/BEST-PRACTICES.md) — solo come snippet manuale.
Pre-snapshot: copia atomica del `parent` VMDK in `F:\CI\Backups\Template_<date>\`. Su errore Pre-snapshot: copia atomica del `parent` VMDK in `F:\CI\Backups\Template_<date>\`. Su errore
di refresh (es. installer rotto), rollback in 1 minuto. di refresh (es. installer rotto), rollback in 1 minuto.
### 2.7 [P2] Health check del runner + monitoraggio Event Log ### 2.7 [P2] [ ] Health check del runner + monitoraggio Event Log
File: [docs/BEST-PRACTICES.md:101-115](docs/BEST-PRACTICES.md) — script presente in doc, non operativo. File: [docs/BEST-PRACTICES.md:101-115](docs/BEST-PRACTICES.md) — script presente in doc, non operativo.
Schedulare ogni 15 min: query `gitea/api/v1/admin/runners`, se `local-windows-runner` non Schedulare ogni 15 min: query `gitea/api/v1/admin/runners`, se `local-windows-runner` non
@@ -322,7 +350,7 @@ EventLog query schedulata o webhook).
## 3. Performance & throughput ## 3. Performance & throughput
### 3.1 [P1] NuGet/pip cache su shared folder ### 3.1 [P1] [ ] NuGet/pip cache su shared folder
File: [docs/OPTIMIZATION.md:93-128](docs/OPTIMIZATION.md), [scripts/Invoke-RemoteBuild.ps1:158-194](scripts/Invoke-RemoteBuild.ps1). File: [docs/OPTIMIZATION.md:93-128](docs/OPTIMIZATION.md), [scripts/Invoke-RemoteBuild.ps1:158-194](scripts/Invoke-RemoteBuild.ps1).
**Motivazione**: `F:\CI\Cache\NuGet` esiste come dir, non viene mai usata. **Motivazione**: `F:\CI\Cache\NuGet` esiste come dir, non viene mai usata.
@@ -343,7 +371,7 @@ $env:PIP_CACHE_DIR = '\\vmware-host\Shared Folders\pip-cache'
``` ```
Dopo lo snapshot refresh la cache va riscaldata una volta — accettabile. Dopo lo snapshot refresh la cache va riscaldata una volta — accettabile.
### 3.2 [P1] Sostituire `Compress-Archive` con 7-Zip o robocopy ### 3.2 [P1] [ ] Sostituire `Compress-Archive` con 7-Zip o robocopy
File: [scripts/Invoke-RemoteBuild.ps1:112,148,185](scripts/Invoke-RemoteBuild.ps1) (3 chiamate `Compress-Archive`). File: [scripts/Invoke-RemoteBuild.ps1:112,148,185](scripts/Invoke-RemoteBuild.ps1) (3 chiamate `Compress-Archive`).
Prerequisito: 7-Zip nel template — vedi §6.6 e sezione "In-VM Git Clone". Prerequisito: 7-Zip nel template — vedi §6.6 e sezione "In-VM Git Clone".
@@ -361,12 +389,12 @@ può prendere 20-40 s.
Misurare con un repo reale: probabile risparmio 10-20 s/build su `nsis-plugin-nsinnounp`. Misurare con un repo reale: probabile risparmio 10-20 s/build su `nsis-plugin-nsinnounp`.
### 3.3 [P2] In-VM clone (`-UseGitClone`) ### 3.3 [P2] [ ] In-VM clone (`-UseGitClone`)
Implementazione disegnata di seguito (sezione "In-VM Git Clone (Ottimizzazione)"). Implementazione disegnata di seguito (sezione "In-VM Git Clone (Ottimizzazione)").
Vincoli sicurezza PAT: vedi §1.5. Tool prerequisiti (Git, 7-Zip): vedi §6.6. Vincoli sicurezza PAT: vedi §1.5. Tool prerequisiti (Git, 7-Zip): vedi §6.6.
Beneficio reale solo per repo > 200 MB con submoduli grandi; misurare prima. Beneficio reale solo per repo > 200 MB con submoduli grandi; misurare prima.
### 3.4 [P3] Pre-warm pool di cloni ### 3.4 [P3] [ ] Pre-warm pool di cloni
File: [docs/OPTIMIZATION.md:133-156](docs/OPTIMIZATION.md). File: [docs/OPTIMIZATION.md:133-156](docs/OPTIMIZATION.md).
Solo se profilo dimostra che `New-BuildVM` + `Wait-VMReady` (~30-60 s) è il collo di bottiglia. Solo se profilo dimostra che `New-BuildVM` + `Wait-VMReady` (~30-60 s) è il collo di bottiglia.
@@ -376,7 +404,7 @@ Implementazione minima: scheduled task ogni 5 min mantiene 2 cloni avviati in
`F:\CI\WarmPool\`; `Invoke-CIJob` fa `Move-Item` di un clone caldo nel suo job dir e parte `F:\CI\WarmPool\`; `Invoke-CIJob` fa `Move-Item` di un clone caldo nel suo job dir e parte
da Phase 4 saltando Phase 2-3. da Phase 4 saltando Phase 2-3.
### 3.5 [P2] vCPU/RAM tuning per workload ### 3.5 [P2] [ ] vCPU/RAM tuning per workload
File: [docs/OPTIMIZATION.md:82-89](docs/OPTIMIZATION.md), VMX template. File: [docs/OPTIMIZATION.md:82-89](docs/OPTIMIZATION.md), VMX template.
`numvcpus=4`, `memsize=6144` è ragionevole ma non ottimo per ogni build. Per `numvcpus=4`, `memsize=6144` è ragionevole ma non ottimo per ogni build. Per
@@ -386,14 +414,14 @@ Considerare:
- VMX pinning: 6 vCPU per build C++ pesanti, 2 vCPU per build .NET piccoli. - VMX pinning: 6 vCPU per build C++ pesanti, 2 vCPU per build .NET piccoli.
- Esporre `-VMCpu` / `-VMMemory` a `Invoke-CIJob.ps1` con override via workflow. - Esporre `-VMCpu` / `-VMMemory` a `Invoke-CIJob.ps1` con override via workflow.
### 3.6 [P2] Benchmark baseline ### 3.6 [P2] [ ] Benchmark baseline
- [ ] Tempo creazione clone + tempo WinRM readiness (per stabilire baseline prima di §3.1/3.2/3.4). - [ ] Tempo creazione clone + tempo WinRM readiness (per stabilire baseline prima di §3.1/3.2/3.4).
--- ---
## 4. Osservabilità & manutenzione ## 4. Osservabilità & manutenzione
### 4.1 [P1] Log strutturati per parsing ### 4.1 [P1] [ ] Log strutturati per parsing
File: [scripts/Invoke-CIJob.ps1:178-187](scripts/Invoke-CIJob.ps1). File: [scripts/Invoke-CIJob.ps1:178-187](scripts/Invoke-CIJob.ps1).
**Motivazione**: i log attuali sono `Write-Host` testuale. Per dashboard / alerting servono **Motivazione**: i log attuali sono `Write-Host` testuale. Per dashboard / alerting servono
@@ -416,7 +444,7 @@ function Write-JobEvent {
Emettere in ogni transizione di fase. Permette grafici (durata media per fase, tasso di Emettere in ogni transizione di fase. Permette grafici (durata media per fase, tasso di
fallimento) con `jq` o Loki/Grafana se in futuro. fallimento) con `jq` o Loki/Grafana se in futuro.
### 4.2 [P2] Metriche su Prometheus textfile ### 4.2 [P2] [ ] Metriche su Prometheus textfile
Generare `F:\CI\Metrics\runner.prom` da uno scheduled task ogni 60s: Generare `F:\CI\Metrics\runner.prom` da uno scheduled task ogni 60s:
``` ```
ci_runner_orphan_vms 0 ci_runner_orphan_vms 0
@@ -427,13 +455,13 @@ ci_runner_active_jobs 1
Se l'homelab ha già Prometheus + node_exporter, basta Se l'homelab ha già Prometheus + node_exporter, basta
`--collector.textfile.directory=F:\CI\Metrics`. `--collector.textfile.directory=F:\CI\Metrics`.
### 4.3 [P1] Disk space alert ### 4.3 [P1] [ ] Disk space alert
File: estendere `Setup-Host.ps1` o creare `scripts/Watch-DiskSpace.ps1`. File: estendere `Setup-Host.ps1` o creare `scripts/Watch-DiskSpace.ps1`.
`F:` riempito = build silenziosamente fallite (linked clone fallirà su `vmrun clone` con `F:` riempito = build silenziosamente fallite (linked clone fallirà su `vmrun clone` con
messaggi criptici). Alert via `eventcreate` o webhook Gitea/Discord quando libero < 50 GB. messaggi criptici). Alert via `eventcreate` o webhook Gitea/Discord quando libero < 50 GB.
### 4.4 [P2] Runbook per incident comuni ### 4.4 [P2] [ ] Runbook per incident comuni
File: nuovo `docs/RUNBOOK.md`. File: nuovo `docs/RUNBOOK.md`.
Documentare con copy-pasta: Documentare con copy-pasta:
@@ -444,7 +472,7 @@ Documentare con copy-pasta:
Ogni voce: sintomo, comando di triage, fix, escalation. Ogni voce: sintomo, comando di triage, fix, escalation.
### 4.5 [P3] Dashboard read-only ### 4.5 [P3] [ ] Dashboard read-only
Una pagina HTML statica generata da `Invoke-CIJob.ps1` (append a `F:\CI\dashboard.html`) Una pagina HTML statica generata da `Invoke-CIJob.ps1` (append a `F:\CI\dashboard.html`)
con ultime 50 build, durata, esito, link agli artifact. Servita da IIS Express o solo con ultime 50 build, durata, esito, link agli artifact. Servita da IIS Express o solo
file:// — utile per debug a colpo d'occhio senza aprire Gitea UI. file:// — utile per debug a colpo d'occhio senza aprire Gitea UI.
@@ -453,7 +481,7 @@ file:// — utile per debug a colpo d'occhio senza aprire Gitea UI.
## 5. Qualità codice & test ## 5. Qualità codice & test
### 5.1 [P1] Pester smoke tests sugli script ### 5.1 [P1] [ ] Pester smoke tests sugli script
File: nuovo `tests/` directory. File: nuovo `tests/` directory.
**Motivazione**: manca un livello di test sotto l'e2e. **Motivazione**: manca un livello di test sotto l'e2e.
@@ -467,7 +495,7 @@ Pester può coprire:
Eseguire in CI tramite il runner stesso (workflow `lint.yml` esiste già — espandere). Eseguire in CI tramite il runner stesso (workflow `lint.yml` esiste già — espandere).
### 5.2 [P2] Modulo PowerShell condiviso `scripts/_Common.psm1` ### 5.2 [P2] [ ] Modulo PowerShell condiviso `scripts/_Common.psm1`
**Duplicazioni da estrarre**: **Duplicazioni da estrarre**:
- `New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck` (3 file). - `New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck` (3 file).
- `ValidatePattern` IP (4 file — vedi §1.4). - `ValidatePattern` IP (4 file — vedi §1.4).
@@ -477,7 +505,7 @@ Eseguire in CI tramite il runner stesso (workflow `lint.yml` esiste già — esp
Risultato: meno LoC, fix in un punto solo, più facile da testare. Risultato: meno LoC, fix in un punto solo, più facile da testare.
### 5.3 [P2] Esecuzione `vmrun` con check exit code uniforme ### 5.3 [P2] [ ] Esecuzione `vmrun` con check exit code uniforme
Pattern attuale ripetuto in tutti gli script che invocano `vmrun`: Pattern attuale ripetuto in tutti gli script che invocano `vmrun`:
```powershell ```powershell
$out = & $VmrunPath ... 2>&1 $out = & $VmrunPath ... 2>&1
@@ -486,7 +514,7 @@ if ($LASTEXITCODE -ne 0) { throw "..." }
Wrapping in `Invoke-VmrunCommand -Operation 'clone' -Args @(...)` riduce errori di copy-paste Wrapping in `Invoke-VmrunCommand -Operation 'clone' -Args @(...)` riduce errori di copy-paste
e centralizza retry/log. e centralizza retry/log.
### 5.4 [P2] PSScriptAnalyzer rules custom ### 5.4 [P2] [ ] PSScriptAnalyzer rules custom
File: [gitea/workflows/lint.yml](gitea/workflows/lint.yml) (già esiste). File: [gitea/workflows/lint.yml](gitea/workflows/lint.yml) (già esiste).
Aggiungere regole specifiche progetto (`PSScriptAnalyzerSettings.psd1` in root): Aggiungere regole specifiche progetto (`PSScriptAnalyzerSettings.psd1` in root):
@@ -495,7 +523,7 @@ Aggiungere regole specifiche progetto (`PSScriptAnalyzerSettings.psd1` in root):
- `PSUseShouldProcessForStateChangingFunctions`. - `PSUseShouldProcessForStateChangingFunctions`.
- Regole custom: vietare hardcoded `F:\CI\` fuori da config (forzare param/env). - Regole custom: vietare hardcoded `F:\CI\` fuori da config (forzare param/env).
### 5.5 [P3] Type hints nei param block ### 5.5 [P3] [ ] Type hints nei param block
Già parzialmente fatto. Estendere a tutti gli script per migliorare IntelliSense e Già parzialmente fatto. Estendere a tutti gli script per migliorare IntelliSense e
validazione runtime. validazione runtime.
@@ -503,7 +531,7 @@ validazione runtime.
## 6. Scalabilità & estensioni ## 6. Scalabilità & estensioni
### 6.1 [P2] Linux Build VM ### 6.1 [P2] [ ] Linux Build VM
**Documento di pianificazione dedicato**: [docs/LINUX-TEMPLATE-SETUP.md](docs/LINUX-TEMPLATE-SETUP.md) **Documento di pianificazione dedicato**: [docs/LINUX-TEMPLATE-SETUP.md](docs/LINUX-TEMPLATE-SETUP.md)
— bozza completa con fasi A→H, decisioni di design, differenze rispetto al template Windows. — bozza completa con fasi A→H, decisioni di design, differenze rispetto al template Windows.
@@ -517,7 +545,7 @@ Sostituire WinRM con SSH key-based; `Invoke-Command` non funziona — usare `ssh
Aggiungere `scripts/_Transport.psm1` con due implementazioni (`WinRM`, `SSH`) dietro la Aggiungere `scripts/_Transport.psm1` con due implementazioni (`WinRM`, `SSH`) dietro la
stessa interfaccia. `Invoke-CIJob.ps1` sceglie dal parametro `-GuestOS`. stessa interfaccia. `Invoke-CIJob.ps1` sceglie dal parametro `-GuestOS`.
### 6.2 [P3] Workflow generico riutilizzabile (composite action) ### 6.2 [P3] [ ] Workflow generico riutilizzabile (composite action)
File: [gitea/workflow-example.yml](gitea/workflow-example.yml). File: [gitea/workflow-example.yml](gitea/workflow-example.yml).
Trasformare in *composite action* Gitea (`.gitea/actions/local-ci-build/action.yml`) Trasformare in *composite action* Gitea (`.gitea/actions/local-ci-build/action.yml`)
@@ -531,7 +559,7 @@ richiamabile da qualsiasi repo:
``` ```
Riduce la duplicazione su nuovi repo e centralizza l'evoluzione del wrapper. Riduce la duplicazione su nuovi repo e centralizza l'evoluzione del wrapper.
### 6.3 [P3] Multi-host runner federation ### 6.3 [P3] [ ] Multi-host runner federation
Quando 4 VM in parallelo non bastano, l'opzione naturale è un secondo host Windows con Quando 4 VM in parallelo non bastano, l'opzione naturale è un secondo host Windows con
stesso `Setup-Host.ps1` registrato come secondo runner Gitea (label `windows-build:host-2`). stesso `Setup-Host.ps1` registrato come secondo runner Gitea (label `windows-build:host-2`).
Già supportato lato Gitea, basta replicare il setup. Già supportato lato Gitea, basta replicare il setup.
@@ -539,7 +567,7 @@ Già supportato lato Gitea, basta replicare il setup.
**Premessa**: prima di scalare orizzontale, profilare se il collo di bottiglia è CPU **Premessa**: prima di scalare orizzontale, profilare se il collo di bottiglia è CPU
(i9-10900X 20T è abbondante) o I/O (NVMe). Se è I/O, una seconda NVMe sullo stesso host basta. (i9-10900X 20T è abbondante) o I/O (NVMe). Se è I/O, una seconda NVMe sullo stesso host basta.
### 6.4 [P3] Build matrix nel workflow ### 6.4 [P3] [ ] Build matrix nel workflow
File: [gitea/workflows/build-nsis.yml](gitea/workflows/build-nsis.yml). File: [gitea/workflows/build-nsis.yml](gitea/workflows/build-nsis.yml).
Attualmente single job. Quando arriverà la VM Linux: Attualmente single job. Quando arriverà la VM Linux:
@@ -550,11 +578,11 @@ strategy:
runs-on: ${{ matrix.target }}-build runs-on: ${{ matrix.target }}-build
``` ```
### 6.5 [P3] Secret injection workflow-level ### 6.5 [P3] [ ] Secret injection workflow-level
Per chiavi di firma (Authenticode, GPG), usare i Gitea secrets nel workflow e passarli a Per chiavi di firma (Authenticode, GPG), usare i Gitea secrets nel workflow e passarli a
`Invoke-CIJob.ps1` come param + env, mai loggati. Stesso modello di §1.5 per il PAT. `Invoke-CIJob.ps1` come param + env, mai loggati. Stesso modello di §1.5 per il PAT.
### 6.6 [P2] Toolchain Tier-1 in Setup-WinBuild2025 ### 6.6 [P2] [ ] Toolchain Tier-1 in Setup-WinBuild2025
File: [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1). File: [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1).
**Motivazione**: toolchain attuale (`.NET SDK`, `Python`, `VS Build Tools`) copre solo C#/Python/MSBuild. **Motivazione**: toolchain attuale (`.NET SDK`, `Python`, `VS Build Tools`) copre solo C#/Python/MSBuild.
@@ -612,7 +640,7 @@ duplicazioni; ogni concern ha **una sola** sorgente di verità.
**Stato attuale (duplicati)**: UAC, Explorer LaunchTo, Server Manager off, DisableCAD, **Stato attuale (duplicati)**: UAC, Explorer LaunchTo, Server Manager off, DisableCAD,
OOBE telemetry, WU services disable — entrambi gli script li toccano. OOBE telemetry, WU services disable — entrambi gli script li toccano.
### 7.1 [P1] Spostare base OS hardening da Setup a Deploy ### 7.1 [P1] [x] Spostare base OS hardening da Setup a Deploy
File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1), File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1),
[template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1). [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1).
@@ -627,7 +655,7 @@ File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1),
- [x] Setup Step 5c (Server Manager / DisableCAD / OOBE) → Assert-Step only - [x] Setup Step 5c (Server Manager / DisableCAD / OOBE) → Assert-Step only
- [x] E2e validation — completata §7.4 (2026-05-10) - [x] E2e validation — completata §7.4 (2026-05-10)
### 7.2 [P1] WU lifecycle — strategia B (Deploy: GPO + Manual; Setup: lifecycle) ### 7.2 [P1] [x] WU lifecycle — strategia B (Deploy: GPO + Manual; Setup: lifecycle)
File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1), File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1),
[template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1). [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1).
@@ -642,13 +670,13 @@ avvia WU via SchTask SYSTEM, poi Step 6b disabilita servizi permanentemente post
- [x] Setup Step 6b: già disabilita wuauserv/UsoSvc + GPO keys — nessuna modifica necessaria - [x] Setup Step 6b: già disabilita wuauserv/UsoSvc + GPO keys — nessuna modifica necessaria
- [x] E2e validation — completata §7.4 (2026-05-10) - [x] E2e validation — completata §7.4 (2026-05-10)
### 7.3 [P2] Update header docs di entrambi gli script ### 7.3 [P2] [x] Update header docs di entrambi gli script
- [x] [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) - [x] [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)
- [x] [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 - [x] [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
- [x] [docs/WINDOWS-TEMPLATE-SETUP.md](docs/WINDOWS-TEMPLATE-SETUP.md): architettura "tre script", VMX path `CI-WinBuild.vmx`, Fase C step list + razionale + tabella validazioni aggiornati - [x] [docs/WINDOWS-TEMPLATE-SETUP.md](docs/WINDOWS-TEMPLATE-SETUP.md): architettura "tre script", VMX path `CI-WinBuild.vmx`, Fase C step list + razionale + tabella validazioni aggiornati
### 7.4 [P2] Test e2e refactor — COMPLETATO 2026-05-10 ### 7.4 [P2] [x] Test e2e refactor — COMPLETATO 2026-05-10
- [x] Deploy stand-alone su VM test → `Get-NetFirewallProfile` tutti `Enabled=False`, `Get-Service wuauserv | Select StartType``Manual`, `NoAutoUpdate=1` GPO presente - [x] Deploy stand-alone su VM test → `Get-NetFirewallProfile` tutti `Enabled=False`, `Get-Service wuauserv | Select StartType``Manual`, `NoAutoUpdate=1` GPO presente
- [x] Prepare dopo Deploy con `-SkipWindowsUpdate` → tutti `Assert-Step` passano `[OK]` senza eseguire Set (log mostra solo validation output) - [x] Prepare dopo Deploy con `-SkipWindowsUpdate` → tutti `Assert-Step` passano `[OK]` senza eseguire Set (log mostra solo validation output)
@@ -661,7 +689,7 @@ avvia WU via SchTask SYSTEM, poi Step 6b disabilita servizi permanentemente post
- Deploy + Setup: autologin Administrator (`AutoAdminLogon=1`) aggiunto in post-install.ps1 e validato in Assert-Step 5c - Deploy + Setup: autologin Administrator (`AutoAdminLogon=1`) aggiunto in post-install.ps1 e validato in Assert-Step 5c
- Nuovi script: `template/Validate-DeployState.ps1`, `template/Validate-SetupState.ps1` - Nuovi script: `template/Validate-DeployState.ps1`, `template/Validate-SetupState.ps1`
### 7.5 [P3] Rinomina Setup → Setup-CITools (opzionale, futuro) ### 7.5 [P3] [ ] Rinomina Setup → Setup-CITools (opzionale, futuro)
Una volta che Setup fa solo build customization, nome `Setup-WinBuild2025` è impreciso — Una volta che Setup fa solo build customization, nome `Setup-WinBuild2025` è impreciso —
non setup-a la build VM, **estende** un template OS già pronto con toolchain CI. non setup-a la build VM, **estende** un template OS già pronto con toolchain CI.
Rinominare in `Setup-CITools.ps1` o `Install-CIToolchain.ps1` rifletterebbe meglio la Rinominare in `Setup-CITools.ps1` o `Install-CIToolchain.ps1` rifletterebbe meglio la
+8 -6
View File
@@ -68,7 +68,6 @@ $deadline = (Get-Date).AddSeconds($TimeoutSeconds)
$attempt = 0 $attempt = 0
$phase = 'vmrun-state' # tracks current phase for informative output $phase = 'vmrun-state' # tracks current phase for informative output
$lastPhase = '' $lastPhase = ''
$wsmOpt = New-WSManSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
Write-Host "[Wait-VMReady] Waiting for VM ready (timeout: ${TimeoutSeconds}s, IP: $IPAddress)..." Write-Host "[Wait-VMReady] Waiting for VM ready (timeout: ${TimeoutSeconds}s, IP: $IPAddress)..."
@@ -108,14 +107,17 @@ while ((Get-Date) -lt $deadline) {
} }
} }
# ── Phase 3: WinRM responsive ──────────────────────────────────────── # ── Phase 3: WinRM port 5986 accepting TCP connections ───────────────
try { # Test-WSMan has no -SessionOption in PS 5.1 and fails cert validation for
Test-WSMan -ComputerName $IPAddress -Port 5986 -UseSSL -SessionOption $wsmOpt -ErrorAction Stop | Out-Null # self-signed certs. TCP open on 5986 = HTTPS listener up = VM ready.
# Success — VM is ready $winrmUp = Test-NetConnection -ComputerName $IPAddress -Port 5986 `
-InformationLevel Quiet -WarningAction SilentlyContinue `
-ErrorAction SilentlyContinue
if ($winrmUp) {
Write-Host "[Wait-VMReady] VM ready after ~$([int]($attempt * $PollIntervalSeconds))s (attempt $attempt)." Write-Host "[Wait-VMReady] VM ready after ~$([int]($attempt * $PollIntervalSeconds))s (attempt $attempt)."
return return
} }
catch { else {
$phase = 'winrm' $phase = 'winrm'
if ($lastPhase -ne $phase) { if ($lastPhase -ne $phase) {
Write-Host "[Wait-VMReady] Phase 3/3: ping OK, waiting for WinRM on port 5986 (HTTPS)..." Write-Host "[Wait-VMReady] Phase 3/3: ping OK, waiting for WinRM on port 5986 (HTTPS)..."
+23 -28
View File
@@ -8,8 +8,9 @@
1. Pre-flight validation: script presence, IP octet range, TCP/5986 reachable 1. Pre-flight validation: script presence, IP octet range, TCP/5986 reachable
2. Configures host WinRM client (TrustedHosts only, HTTPS needs no AllowUnencrypted) — restored on exit 2. Configures host WinRM client (TrustedHosts only, HTTPS needs no AllowUnencrypted) — restored on exit
3. Validates host WinRM TrustedHosts applied correctly 3. Validates host WinRM TrustedHosts applied correctly
4. Tests WinRM HTTPS connectivity (Test-WSMan -UseSSL -Port 5986) — fails fast with actionable message 4. Opens PSSession -UseSSL -Port 5986 -SkipCACheck — fails fast with actionable message
5. Opens PSSession to the VM (PS 5.1 Test-WSMan has no -SessionOption, so PSSession is used as the connectivity probe)
5. PSSession open — step 4 and 5 are now merged
6. Ensures C:\CI exists on guest; validates creation 6. Ensures C:\CI exists on guest; validates creation
7. Copies Setup-WinBuild2025.ps1 into the VM; validates file present + size match 7. Copies Setup-WinBuild2025.ps1 into the VM; validates file present + size match
8. Runs Setup-WinBuild2025.ps1 inside the VM with the given parameters 8. Runs Setup-WinBuild2025.ps1 inside the VM with the given parameters
@@ -260,7 +261,6 @@ else {
} }
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck $sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
$wsmOpt = New-WSManSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
# ── Configure HOST-side WinRM client (TrustedHosts for HTTPS Basic auth) ────── # ── Configure HOST-side WinRM client (TrustedHosts for HTTPS Basic auth) ──────
# HTTPS/5986 does not require AllowUnencrypted on the host side. Only TrustedHosts # HTTPS/5986 does not require AllowUnencrypted on the host side. Only TrustedHosts
@@ -288,16 +288,21 @@ catch {
} }
Write-Host "[Prepare] Testing WinRM HTTPS connectivity to $VMIPAddress`:5986..." # ── Open session (Test-WSMan skipped — PS 5.1 Test-WSMan has no -SessionOption for HTTPS) ──
try { # New-PSSession with -SkipCACheck handles cert validation for self-signed lab cert.
Test-WSMan -ComputerName $VMIPAddress -Port 5986 -UseSSL ` Write-Host "[Prepare] Opening PSSession to $VMIPAddress`:5986 (HTTPS)..."
-SessionOption $wsmOpt -Credential $credential ` $session = try {
-Authentication Basic -ErrorAction Stop | Out-Null New-PSSession `
Write-Host "[Prepare] WinRM HTTPS reachable." -ComputerName $VMIPAddress `
} -Credential $credential `
catch { -SessionOption $sessionOptions `
-UseSSL `
-Port 5986 `
-Authentication Basic `
-ErrorAction Stop
} catch {
Write-Error @" Write-Error @"
[Prepare] Cannot reach $VMIPAddress via WinRM HTTPS (port 5986). [Prepare] Cannot open PSSession to $VMIPAddress via WinRM HTTPS (port 5986).
Ensure: Ensure:
- The VM is running and on VMnet8 (NAT) with internet access - The VM is running and on VMnet8 (NAT) with internet access
@@ -311,17 +316,6 @@ Error: $_
exit 1 exit 1
} }
# ── Open session ──────────────────────────────────────────────────────────────
Write-Host "[Prepare] Opening PSSession..."
$session = New-PSSession `
-ComputerName $VMIPAddress `
-Credential $credential `
-SessionOption $sessionOptions `
-UseSSL `
-Port 5986 `
-Authentication Basic `
-ErrorAction Stop
try { try {
# ── Copy Setup-WinBuild2025.ps1 into the VM ─────────────────────────────── # ── Copy Setup-WinBuild2025.ps1 into the VM ───────────────────────────────
$setupScript = Join-Path $scriptDir 'Setup-WinBuild2025.ps1' $setupScript = Join-Path $scriptDir 'Setup-WinBuild2025.ps1'
@@ -415,11 +409,12 @@ try {
while ((Get-Date) -lt $deadline) { while ((Get-Date) -lt $deadline) {
Start-Sleep -Seconds 10 Start-Sleep -Seconds 10
try { try {
$wsm = New-WSManSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck # Test-WSMan has no -SessionOption in PS 5.1 — use New-PSSession as probe.
$r = Test-WSMan -ComputerName $IP -Port 5986 -UseSSL ` $probe = New-PSSession -ComputerName $IP -Port 5986 -UseSSL `
-SessionOption $wsm -Credential $Cred ` -SessionOption $SessionOptions -Credential $Cred `
-Authentication Basic -ErrorAction Stop -Authentication Basic -ErrorAction Stop
if ($r) { return $true } Remove-PSSession $probe -ErrorAction SilentlyContinue
return $true
} catch { } } catch { }
} }
return $false return $false