security(sprint1): §1.1/1.3/1.4 — WinRM HTTPS/5986, SHA256 pinning infra, IP regex per-octet
§1.4 — Replace loose IP ValidatePattern with per-octet regex in 4 scripts
(Invoke-CIJob, Invoke-RemoteBuild, Get-BuildArtifacts, Wait-VMReady)
§1.1 — Migrate all WinRM connections from HTTP/5985 to HTTPS/5986
- Deploy post-install.ps1: remove AllowUnencrypted=true; self-signed cert + HTTPS listener
already present; firewall rule restricted to 192.168.79.0/24
- Setup-WinBuild2025.ps1: assert AllowUnencrypted=false
- Prepare-WinBuild2025.ps1: preflight uses TCP/5986; Test-WSMan -UseSSL -Port 5986;
New-PSSession -UseSSL -Port 5986; host AllowUnencrypted save/restore removed (not needed for HTTPS)
- Invoke-RemoteBuild, Get-BuildArtifacts: New-PSSession -UseSSL -Port 5986 -Authentication Basic
- Wait-VMReady: New-WSManSessionOption + Test-WSMan -Port 5986 -UseSSL
- Validate-DeployState, Validate-SetupState: Invoke-Command -UseSSL -Port 5986,
AllowUnencrypted check → false; AllowUnencrypted host-side removed from both
- Docs: PLAN, README, ARCHITECTURE, BEST-PRACTICES, HOST-SETUP, WINDOWS-TEMPLATE-SETUP,
LINUX-TEMPLATE-SETUP, TODO updated
§1.3 — SHA256 hash pinning infrastructure
- Assert-Hash function + $script:Hashes hashtable added to Setup-WinBuild2025.ps1
- Assert-Hash called after dotnet-install.ps1, python installer, vs_buildtools.exe downloads
- Hashes initialized to '' (warn-skip); must be filled before next snapshot refresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
- Gitea self-hosted — `http://10.10.20.11:3100`
|
- Gitea self-hosted — `http://10.10.20.11:3100`
|
||||||
- act_runner v1.0.2 — Windows service su host, label `windows-build`
|
- act_runner v1.0.2 — Windows service su host, label `windows-build`
|
||||||
- Build VMs: subnet VMnet8 NAT — `192.168.79.0/24`
|
- Build VMs: subnet VMnet8 NAT — `192.168.79.0/24`
|
||||||
- WinRM HTTP/5985 (Basic auth, lab-only)
|
- WinRM HTTPS/5986 (Basic auth over HTTPS, self-signed cert, SkipCACheck lab-only)
|
||||||
|
|
||||||
### Architettura implementata
|
### Architettura implementata
|
||||||
1. Gitea Actions enqueue job → act_runner lo prende
|
1. Gitea Actions enqueue job → act_runner lo prende
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Ogni build gira in una VM Windows Server 2025 **usa e getta**, ripristinata dall
|
|||||||
| .NET SDK | 10.0.203 |
|
| .NET SDK | 10.0.203 |
|
||||||
| Python | 3.13.3 |
|
| Python | 3.13.3 |
|
||||||
| Rete | VMnet8 NAT — `192.168.79.0/24` |
|
| Rete | VMnet8 NAT — `192.168.79.0/24` |
|
||||||
| WinRM | HTTP/5985, Basic auth (lab-only) |
|
| WinRM | HTTPS/5986, Basic auth, self-signed cert (SkipCACheck — lab-only) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -181,6 +181,7 @@ e aggiorna `-RepoUrl`, `-BuildCommand` e `-GuestArtifactSource`.
|
|||||||
|
|
||||||
## Note di sicurezza
|
## Note di sicurezza
|
||||||
|
|
||||||
WinRM in modalità HTTP/Basic è **accettabile solo in ambiente lab isolato**.
|
WinRM usa **HTTPS/5986** con certificato self-signed e Basic auth.
|
||||||
Per ambienti di produzione o condivisi, migrare a HTTPS/5986 con certificato self-signed
|
`-SkipCACheck`/`-SkipCNCheck` sono accettabili in lab isolato (il cert non è di una CA trusted).
|
||||||
e rimuovere `AllowUnencrypted=true`. Vedi [docs/BEST-PRACTICES.md](docs/BEST-PRACTICES.md).
|
Per ambienti condivisi o di produzione, usare un certificato CA valida e rimuovere `-SkipCACheck`.
|
||||||
|
Vedi [docs/BEST-PRACTICES.md](docs/BEST-PRACTICES.md).
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
```powershell
|
```powershell
|
||||||
Enable-PSRemoting -Force -SkipNetworkProfileCheck
|
Enable-PSRemoting -Force -SkipNetworkProfileCheck
|
||||||
Set-Item WSMan:\localhost\Service\Auth\Basic $true -Force
|
Set-Item WSMan:\localhost\Service\Auth\Basic $true -Force
|
||||||
Set-Item WSMan:\localhost\Service\AllowUnencrypted $true -Force
|
# AllowUnencrypted left false — Deploy post-install.ps1 configures HTTPS/5986 listener
|
||||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
|
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
|
||||||
```
|
```
|
||||||
- [x] **Attiva Windows Server 2025** (prima dello snapshot — i linked clone ereditano l'attivazione):
|
- [x] **Attiva Windows Server 2025** (prima dello snapshot — i linked clone ereditano l'attivazione):
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
```
|
```
|
||||||
Installa: WinRM config, utente `ci_build`, .NET SDK 10.0, VS Build Tools 2026, Python 3.13.3.
|
Installa: WinRM config, utente `ci_build`, .NET SDK 10.0, VS Build Tools 2026, Python 3.13.3.
|
||||||
Lo script esegue validazione automatica ad ogni passaggio (`Assert-Step`):
|
Lo script esegue validazione automatica ad ogni passaggio (`Assert-Step`):
|
||||||
- Pre-flight: IP ottetti 0-255, TCP/5985 raggiungibile, Setup-WinBuild2025.ps1 presente
|
- Pre-flight: IP ottetti 0-255, TCP/5986 raggiungibile, Setup-WinBuild2025.ps1 presente
|
||||||
- Host WinRM: AllowUnencrypted, TrustedHosts
|
- Host WinRM: AllowUnencrypted, TrustedHosts
|
||||||
- Guest prep: `C:\CI` esiste, file copiato, size match
|
- Guest prep: `C:\CI` esiste, file copiato, size match
|
||||||
- Post-setup remoto (9 check): WinRM, user+admin, UAC, firewall, .NET, Python, MSBuild, CI dirs
|
- Post-setup remoto (9 check): WinRM, user+admin, UAC, firewall, .NET, Python, MSBuild, CI dirs
|
||||||
@@ -148,25 +148,22 @@
|
|||||||
|
|
||||||
## 1. Sicurezza & hardening
|
## 1. Sicurezza & hardening
|
||||||
|
|
||||||
### 1.1 [P0] Migrare WinRM da HTTP/Basic a HTTPS/5986
|
### 1.1 [P0] Migrare WinRM da HTTP/Basic a HTTPS/5986 — COMPLETATO 2026-05-10
|
||||||
File: [template/Setup-WinBuild2025.ps1:204-241](template/Setup-WinBuild2025.ps1) (Step 3 WinRM), [template/Deploy-WinBuild2025.ps1:524-534](template/Deploy-WinBuild2025.ps1) (Enable-PSRemoting + AllowUnencrypted/Basic), [scripts/Invoke-RemoteBuild.ps1:83-91](scripts/Invoke-RemoteBuild.ps1), [scripts/Get-BuildArtifacts.ps1:66-75](scripts/Get-BuildArtifacts.ps1).
|
|
||||||
|
|
||||||
**Motivazione**: la combo `AllowUnencrypted=true` + `Auth/Basic=true` invia credenziali del
|
|
||||||
`ci_build` (admin) in chiaro su VMnet8. Anche in lab isolato, l'host può essere compromesso
|
|
||||||
via altri vettori e scrapare la rete VMware. Il piano è già in `docs/BEST-PRACTICES.md §2`;
|
|
||||||
manca solo l'esecuzione.
|
|
||||||
|
|
||||||
**Azioni**:
|
**Azioni**:
|
||||||
- [ ] Generare cert self-signed nel template *prima* dello snapshot.
|
- [x] Generare cert self-signed nel template *prima* dello snapshot (già in Deploy post-install.ps1).
|
||||||
- [ ] Sostituire `New-PSSession -ComputerName $ip` con `-UseSSL -Port 5986` in `Invoke-RemoteBuild.ps1` e `Get-BuildArtifacts.ps1`.
|
- [x] Sostituire `New-PSSession -ComputerName $ip` con `-UseSSL -Port 5986 -Authentication Basic` in `Invoke-RemoteBuild.ps1`, `Get-BuildArtifacts.ps1`, `Prepare-WinBuild2025.ps1`, `Validate-*.ps1`.
|
||||||
- [ ] Rimuovere `AllowUnencrypted=true` dal `Setup-WinBuild2025.ps1`.
|
- [x] Rimuovere `AllowUnencrypted=true` da Deploy post-install.ps1 e Setup-WinBuild2025.ps1 (assertion → false).
|
||||||
- [ ] Tenere `-SkipCACheck` lato host: per cert lab self-signed è accettabile, ma documentare la motivazione inline.
|
- [x] `-SkipCACheck`/`-SkipCNCheck`/`-SkipRevocationCheck` mantenuti nei `New-PSSessionOption` e `New-WSManSessionOption` (cert lab self-signed — documentato inline).
|
||||||
- [ ] Regole firewall — limitare WinRM alla subnet build VM (`192.168.79.0/24` — VMnet8 NAT).
|
- [x] Regola firewall `WinRM-HTTPS` in Deploy ristretta a `RemoteAddress '192.168.79.0/24'`.
|
||||||
|
- [x] `Wait-VMReady.ps1` usa `New-WSManSessionOption` + `Test-WSMan -Port 5986 -UseSSL`.
|
||||||
|
- [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`.
|
||||||
|
|
||||||
### 1.2 [P0] Restringere `TrustedHosts` lato host (audit-only — già coperto)
|
### 1.2 [P0] Restringere `TrustedHosts` lato host (audit-only — già coperto)
|
||||||
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:270-294](template/Prepare-WinBuild2025.ps1:270) appende `$VMIPAddress` e ripristina nel `finally` (riga 617)
|
- [template/Prepare-WinBuild2025.ps1](template/Prepare-WinBuild2025.ps1) appende `$VMIPAddress` ai `TrustedHosts` e ripristina nel `finally` (post-§1.1: solo TrustedHosts, no AllowUnencrypted)
|
||||||
- [docs/HOST-SETUP.md:121-126](docs/HOST-SETUP.md:121) raccomanda `'192.168.79.*'` in setup manuale
|
- [docs/HOST-SETUP.md:121-126](docs/HOST-SETUP.md:121) raccomanda `'192.168.79.*'` in setup manuale
|
||||||
|
|
||||||
**Azione residua**: audit eventuali host già configurati con `*` (eredità manuale) e
|
**Azione residua**: audit eventuali host già configurati con `*` (eredità manuale) e
|
||||||
@@ -175,32 +172,28 @@ 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
|
### 1.3 [P0] Pinning hash SHA256 degli installer — STRUTTURA AGGIUNTA 2026-05-10
|
||||||
File: [template/Setup-WinBuild2025.ps1:707-755](template/Setup-WinBuild2025.ps1) (Step 8 Python), [template/Setup-WinBuild2025.ps1:757-885](template/Setup-WinBuild2025.ps1) (Step 9 VS Build Tools), [template/Setup-WinBuild2025.ps1:660-705](template/Setup-WinBuild2025.ps1) (Step 7 dotnet-install.ps1).
|
|
||||||
|
|
||||||
**Motivazione**: Python, `dotnet-install.ps1`, `vs_buildtools.exe` vengono scaricati senza
|
`$script:Hashes` + `Assert-Hash` aggiunti a [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1).
|
||||||
verifica integrità. Un MITM nella rete dell'host (anche temporaneo) può iniettare binari
|
`Assert-Hash` viene chiamato dopo ogni download (dotnet-install.ps1, python installer, vs_buildtools.exe).
|
||||||
compromessi nel template, che poi viene snapshottato e usato da *ogni* build.
|
Se l'hash è `''` viene emesso un warning ma l'esecuzione continua (non-breaking).
|
||||||
|
|
||||||
Pattern minimale:
|
**Azione residua — OBBLIGATORIA prima del prossimo refresh snapshot**:
|
||||||
```powershell
|
- [ ] Scaricare i tre file, calcolare hash, riempire `$script:Hashes` in Setup-WinBuild2025.ps1:
|
||||||
$expected = '6F25A7DF...' # SHA256 pinnato per la versione esatta
|
```powershell
|
||||||
$actual = (Get-FileHash $pyInstallerPath -Algorithm SHA256).Hash.ToLower()
|
# Inside or outside VM — qualsiasi PowerShell:
|
||||||
if ($actual -ne $expected.ToLower()) { throw "Hash mismatch: $actual" }
|
(Get-FileHash 'python-3.13.3-amd64.exe' -Algorithm SHA256).Hash # → $script:Hashes['Python']
|
||||||
```
|
(Get-FileHash 'dotnet-install.ps1' -Algorithm SHA256).Hash # → $script:Hashes['DotNetInstallScript']
|
||||||
Aggiungere un blocco hash per ogni download e bloccarli in costanti `$script:Hashes`.
|
(Get-FileHash 'vs_buildtools.exe' -Algorithm SHA256).Hash # → $script:Hashes['VSBuildToolsBootstrapper']
|
||||||
Aggiornare al cambio versione (è raro).
|
```
|
||||||
|
- [ ] Verificare che `Assert-Hash` throwi correttamente su hash errato (test con hash fake).
|
||||||
|
|
||||||
### 1.4 [P1] Validazione IP per-ottetto
|
### 1.4 [P1] Validazione IP per-ottetto — COMPLETATO 2026-05-10
|
||||||
File: [scripts/Invoke-CIJob.ps1:106](scripts/Invoke-CIJob.ps1), [scripts/Invoke-RemoteBuild.ps1:52](scripts/Invoke-RemoteBuild.ps1), [scripts/Get-BuildArtifacts.ps1:42](scripts/Get-BuildArtifacts.ps1), [scripts/Wait-VMReady.ps1:43](scripts/Wait-VMReady.ps1).
|
|
||||||
|
|
||||||
**Motivazione**: `'^(\d{1,3}\.){3}\d{1,3}$'` accetta `999.999.999.999`. Sostituire con regex
|
Regex per-ottetto sostituita in tutti e 4 i file:
|
||||||
per-ottetto:
|
`'^((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)$'`
|
||||||
```powershell
|
|
||||||
$ipv4 = '^((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).
|
||||||
[ValidatePattern($ipv4)]
|
|
||||||
```
|
|
||||||
Estrarre la costante in un modulo condiviso `scripts/_Common.psm1` per evitare drift fra i 4 file (vedi §5.2).
|
|
||||||
|
|
||||||
### 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
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ The runner host **never executes build tools directly**. Its only role is orches
|
|||||||
│ │ ├── Clone_Job_002.vmx │ │
|
│ │ ├── Clone_Job_002.vmx │ │
|
||||||
│ │ └── Clone_Job_003.vmx │ │
|
│ │ └── Clone_Job_003.vmx │ │
|
||||||
│ └───────────────────────────────────────────────────────────────────┘ │
|
│ └───────────────────────────────────────────────────────────────────┘ │
|
||||||
│ │ WinRM :5985 (192.168.79.0/24 NAT) │
|
│ │ WinRM :5986 HTTPS (192.168.79.0/24 NAT) │
|
||||||
└──────────────────────────────────┼──────────────────────────────────────┘
|
└──────────────────────────────────┼──────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
┌────────────────────────┼──────────────────────────┐
|
┌────────────────────────┼──────────────────────────┐
|
||||||
@@ -105,8 +105,8 @@ The runner host **never executes build tools directly**. Its only role is orches
|
|||||||
|
|
||||||
### WinRM / PowerShell Remoting
|
### WinRM / PowerShell Remoting
|
||||||
- Default transport for remote build execution inside VMs
|
- Default transport for remote build execution inside VMs
|
||||||
- Port: **5985** (HTTP, in-lab use); migrate to 5986/HTTPS for hardened setups
|
- Port: **5986** (HTTPS, self-signed cert, `AllowUnencrypted=false`)
|
||||||
- Authentication: **Basic** (unencrypted — acceptable for isolated lab)
|
- Authentication: **Basic over HTTPS** (`-SkipCACheck` for lab self-signed cert)
|
||||||
- Used for:
|
- Used for:
|
||||||
- Transferring source: `Compress-Archive` on host → WinRM copy → `Expand-Archive` in guest
|
- Transferring source: `Compress-Archive` on host → WinRM copy → `Expand-Archive` in guest
|
||||||
- Running build commands (`Invoke-Command`)
|
- Running build commands (`Invoke-Command`)
|
||||||
@@ -133,7 +133,7 @@ Build VMs:
|
|||||||
...
|
...
|
||||||
Clone_Job_N → 192.168.79.1xx
|
Clone_Job_N → 192.168.79.1xx
|
||||||
|
|
||||||
WinRM port 5985 (HTTP) on each VM IP.
|
WinRM port 5986 (HTTPS, self-signed) on each VM IP.
|
||||||
VMs have internet access via NAT — required for pip/nuget during build.
|
VMs have internet access via NAT — required for pip/nuget during build.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+12
-33
@@ -30,47 +30,26 @@ $cred = Get-StoredCredential -Target 'BuildVMGuest'
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. WinRM Security
|
## 2. WinRM Security — HTTPS/5986 (implementato 2026-05-10)
|
||||||
|
|
||||||
### Current setup (HTTP / port 5985)
|
### Setup attuale (HTTPS / port 5986)
|
||||||
|
|
||||||
Acceptable for an **isolated lab network** where:
|
`Deploy-WinBuild2025.ps1` post-install.ps1 crea un certificato self-signed e configura
|
||||||
- Build VMs sono su VMnet8 NAT (192.168.79.0/24) — raggiungibili dall'host, internet via NAT
|
il listener HTTPS/5986 **prima** dello snapshot `BaseClean`. `AllowUnencrypted=false`.
|
||||||
- No external traffic reaches port 5985
|
|
||||||
- Credentials are managed via Windows Credential Manager
|
|
||||||
|
|
||||||
### Recommended upgrade: WinRM over HTTPS (port 5986)
|
- Build VMs su VMnet8 NAT (192.168.79.0/24) — accesso solo dall'host
|
||||||
|
- Port 5986 firewall rule ristretta a `RemoteAddress '192.168.79.0/24'`
|
||||||
|
- Credentials via Windows Credential Manager (target `BuildVMGuest`)
|
||||||
|
|
||||||
|
Tutti gli script host usano:
|
||||||
```powershell
|
```powershell
|
||||||
# Inside the template VM (before taking BaseClean snapshot):
|
|
||||||
|
|
||||||
# Create self-signed certificate
|
|
||||||
$cert = New-SelfSignedCertificate `
|
|
||||||
-Subject "CN=$(hostname)" `
|
|
||||||
-CertStoreLocation Cert:\LocalMachine\My `
|
|
||||||
-KeyUsage DigitalSignature, KeyEncipherment `
|
|
||||||
-KeyAlgorithm RSA `
|
|
||||||
-KeyLength 2048
|
|
||||||
|
|
||||||
# Create HTTPS WinRM listener
|
|
||||||
New-WSManInstance WinRM/Config/Listener `
|
|
||||||
-SelectorSet @{ Transport = 'HTTPS'; Address = '*' } `
|
|
||||||
-ValueSet @{ Hostname = $(hostname); CertificateThumbprint = $cert.Thumbprint }
|
|
||||||
|
|
||||||
# Allow port 5986
|
|
||||||
New-NetFirewallRule -Name 'WinRM-HTTPS-CI' -DisplayName 'WinRM HTTPS CI' `
|
|
||||||
-Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow
|
|
||||||
```
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# On the HOST (in scripts), use HTTPS session options:
|
|
||||||
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
||||||
$session = New-PSSession -ComputerName $ip -Port 5986 -UseSSL `
|
$session = New-PSSession -ComputerName $ip -Port 5986 -UseSSL -Authentication Basic `
|
||||||
-Credential $cred -SessionOption $sessionOptions
|
-Credential $cred -SessionOption $sessionOptions
|
||||||
```
|
```
|
||||||
|
|
||||||
> `-SkipCACheck` is acceptable for a self-signed cert in an isolated lab. Do NOT
|
> `-SkipCACheck`/`-SkipCNCheck` sono accettabili per un cert self-signed in lab isolato.
|
||||||
> use this against externally accessible machines.
|
> Non usare contro macchine accessibili dall'esterno — usare una CA trusted in quel caso.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -251,7 +230,7 @@ Invoke-Command -Session $session -ScriptBlock {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Build VMs can reach:
|
Build VMs can reach:
|
||||||
- The host via VMnet8 gateway (WinRM on port 5985)
|
- The host via VMnet8 gateway (WinRM HTTPS on port 5986)
|
||||||
- Internet via VMware NAT (for pip, nuget, npm at build time)
|
- Internet via VMware NAT (for pip, nuget, npm at build time)
|
||||||
- Gitea server if on LAN reachable via NAT gateway
|
- Gitea server if on LAN reachable via NAT gateway
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -120,10 +120,10 @@ Allo `Setup-Host.ps1` exit 0:
|
|||||||
|
|
||||||
3. **Configura host WinRM client** (necessario per Prepare-WinBuild2025.ps1):
|
3. **Configura host WinRM client** (necessario per Prepare-WinBuild2025.ps1):
|
||||||
```powershell
|
```powershell
|
||||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force
|
# AllowUnencrypted non serve — Prepare usa HTTPS/5986 (nessun testo in chiaro)
|
||||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '192.168.79.*' -Force -Concatenate
|
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '192.168.79.*' -Force -Concatenate
|
||||||
```
|
```
|
||||||
*Note*: Prepare-WinBuild2025.ps1 imposta queste in modo transitorio e le ripristina nel `finally`.
|
*Note*: Prepare-WinBuild2025.ps1 aggiunge l'IP specifico della VM in modo transitorio e lo ripristina nel `finally`.
|
||||||
|
|
||||||
4. **Provisiona il template VM** — vedi [WINDOWS-TEMPLATE-SETUP.md](WINDOWS-TEMPLATE-SETUP.md)
|
4. **Provisiona il template VM** — vedi [WINDOWS-TEMPLATE-SETUP.md](WINDOWS-TEMPLATE-SETUP.md)
|
||||||
|
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ Equivalente di `Prepare-WinBuild2025.ps1` per Linux. Usa SSH invece di WinRM.
|
|||||||
|
|
||||||
| Aspetto | Windows | Linux |
|
| Aspetto | Windows | Linux |
|
||||||
| ----------------------- | -------------------------------------- | ------------------------------------------- |
|
| ----------------------- | -------------------------------------- | ------------------------------------------- |
|
||||||
| Transport | WinRM HTTP/5985 + Basic | SSH/22 + ed25519 key |
|
| Transport | WinRM HTTPS/5986 + Basic (-SkipCACheck) | SSH/22 + ed25519 key |
|
||||||
| Auth fluid | `ci_build` admin + UAC off + LATFP=1 | `ci_build` + sudoers NOPASSWD |
|
| Auth fluid | `ci_build` admin + UAC off + LATFP=1 | `ci_build` + sudoers NOPASSWD |
|
||||||
| Trasferimento file | `Copy-Item -ToSession` | `scp` o `rsync` |
|
| Trasferimento file | `Copy-Item -ToSession` | `scp` o `rsync` |
|
||||||
| Esecuzione remota | `Invoke-Command -Session` | `ssh user@host 'cmd'` |
|
| Esecuzione remota | `Invoke-Command -Session` | `ssh user@host 'cmd'` |
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ winrm quickconfig -q
|
|||||||
```powershell
|
```powershell
|
||||||
Enable-PSRemoting -Force -SkipNetworkProfileCheck
|
Enable-PSRemoting -Force -SkipNetworkProfileCheck
|
||||||
Set-Item WSMan:\localhost\Service\Auth\Basic $true -Force
|
Set-Item WSMan:\localhost\Service\Auth\Basic $true -Force
|
||||||
Set-Item WSMan:\localhost\Service\AllowUnencrypted $true -Force
|
# AllowUnencrypted left false — Deploy post-install.ps1 creates HTTPS/5986 listener
|
||||||
|
# Basic auth is secure over HTTPS; host connects on port 5986 with -SkipCACheck
|
||||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
|
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -110,12 +111,12 @@ cd N:\Code\Workspace\Local-CI-CD-System\template
|
|||||||
|
|
||||||
Ogni step ha validazione `Assert-Step` (throw on failure, `[OK]` su pass):
|
Ogni step ha validazione `Assert-Step` (throw on failure, `[OK]` su pass):
|
||||||
|
|
||||||
1. **Pre-flight**: script dir presente, `Setup-WinBuild2025.ps1` presente, IP ottetti 0-255, TCP/5985 reachable
|
1. **Pre-flight**: script dir presente, `Setup-WinBuild2025.ps1` presente, IP ottetti 0-255, TCP/5986 reachable
|
||||||
2. **Configura host WinRM client**: `AllowUnencrypted=true`, aggiunge `$VMIPAddress` ai `TrustedHosts`
|
2. **Configura host WinRM client**: aggiunge `$VMIPAddress` ai `TrustedHosts` (no `AllowUnencrypted` — HTTPS non lo richiede)
|
||||||
- Salva valori precedenti, ripristinati nel `finally` (host posture invariata)
|
- Salva valore precedente, ripristinato nel `finally` (host posture invariata)
|
||||||
3. **Validazione host WinRM**: `AllowUnencrypted=true`, `TrustedHosts` include IP
|
3. **Validazione host WinRM**: `TrustedHosts` include IP
|
||||||
4. **Test connettività**: `Test-WSMan -Authentication Basic` con credenziali admin
|
4. **Test connettività**: `Test-WSMan -Port 5986 -UseSSL -Authentication Basic` con credenziali admin
|
||||||
5. **Apre PSSession** verso la VM
|
5. **Apre PSSession** `-UseSSL -Port 5986` verso la VM
|
||||||
6. **Crea `C:\CI`** in guest, valida creazione
|
6. **Crea `C:\CI`** in guest, valida creazione
|
||||||
7. **Copia `Setup-WinBuild2025.ps1`** in `C:\CI\Setup-WinBuild2025.ps1`, valida size match
|
7. **Copia `Setup-WinBuild2025.ps1`** in `C:\CI\Setup-WinBuild2025.ps1`, valida size match
|
||||||
8. **Esegue `Setup-WinBuild2025.ps1`** dentro la VM (vedi Fase C)
|
8. **Esegue `Setup-WinBuild2025.ps1`** dentro la VM (vedi Fase C)
|
||||||
@@ -193,7 +194,7 @@ Final Pre-snapshot gate — 7 check cross-cutting + no installer leftover
|
|||||||
| ---- | ---------- | ------------------------------------------------------------------------------ |
|
| ---- | ---------- | ------------------------------------------------------------------------------ |
|
||||||
| 1 | Assert | `Get-NetFirewallProfile -Profile Domain/Private/Public → Enabled=False` |
|
| 1 | Assert | `Get-NetFirewallProfile -Profile Domain/Private/Public → Enabled=False` |
|
||||||
| 2 | Assert | GPO DisableAntiSpyware=1 (set da Deploy) |
|
| 2 | Assert | GPO DisableAntiSpyware=1 (set da Deploy) |
|
||||||
| 3 | Assert | WinRM Running+Automatic, AllowUnencrypted=true, Auth/Basic=true, MaxMem≥2048 |
|
| 3 | Assert | WinRM Running+Automatic, AllowUnencrypted=false (HTTPS-only), Auth/Basic=true, MaxMem≥2048 |
|
||||||
| 4 | Operativo | User exists, enabled, PasswordNeverExpires, member of Administrators |
|
| 4 | Operativo | User exists, enabled, PasswordNeverExpires, member of Administrators |
|
||||||
| 4b | Assert | EnableLUA=0, LocalAccountTokenFilterPolicy=1 |
|
| 4b | Assert | EnableLUA=0, LocalAccountTokenFilterPolicy=1 |
|
||||||
| 5 | Operativo | Ogni dir Test-Path -PathType Container |
|
| 5 | Operativo | Ogni dir Test-Path -PathType Container |
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[ValidatePattern('^(\d{1,3}\.){3}\d{1,3}$')]
|
[ValidatePattern('^((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)$')]
|
||||||
[string] $IPAddress,
|
[string] $IPAddress,
|
||||||
|
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -70,6 +70,9 @@ $session = New-PSSession `
|
|||||||
-ComputerName $IPAddress `
|
-ComputerName $IPAddress `
|
||||||
-Credential $Credential `
|
-Credential $Credential `
|
||||||
-SessionOption $sessionOptions `
|
-SessionOption $sessionOptions `
|
||||||
|
-UseSSL `
|
||||||
|
-Port 5986 `
|
||||||
|
-Authentication Basic `
|
||||||
-ErrorAction Stop
|
-ErrorAction Stop
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ param(
|
|||||||
|
|
||||||
[string] $ArtifactBaseDir = 'F:\CI\Artifacts',
|
[string] $ArtifactBaseDir = 'F:\CI\Artifacts',
|
||||||
|
|
||||||
[ValidatePattern('^(\d{1,3}\.){3}\d{1,3}$')]
|
[ValidatePattern('^((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)$')]
|
||||||
[string] $VMIPAddress = '', # if empty, auto-detected via vmrun getGuestIPAddress
|
[string] $VMIPAddress = '', # if empty, auto-detected via vmrun getGuestIPAddress
|
||||||
|
|
||||||
[string] $GuestCredentialTarget = 'BuildVMGuest',
|
[string] $GuestCredentialTarget = 'BuildVMGuest',
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[ValidatePattern('^(\d{1,3}\.){3}\d{1,3}$')]
|
[ValidatePattern('^((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)$')]
|
||||||
[string] $IPAddress,
|
[string] $IPAddress,
|
||||||
|
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -88,6 +88,9 @@ $session = New-PSSession `
|
|||||||
-ComputerName $IPAddress `
|
-ComputerName $IPAddress `
|
||||||
-Credential $Credential `
|
-Credential $Credential `
|
||||||
-SessionOption $sessionOptions `
|
-SessionOption $sessionOptions `
|
||||||
|
-UseSSL `
|
||||||
|
-Port 5986 `
|
||||||
|
-Authentication Basic `
|
||||||
-ErrorAction Stop
|
-ErrorAction Stop
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ param(
|
|||||||
[string] $VMPath,
|
[string] $VMPath,
|
||||||
|
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[ValidatePattern('^(\d{1,3}\.){3}\d{1,3}$')]
|
[ValidatePattern('^((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)$')]
|
||||||
[string] $IPAddress,
|
[string] $IPAddress,
|
||||||
|
|
||||||
[ValidateRange(30, 600)]
|
[ValidateRange(30, 600)]
|
||||||
@@ -52,7 +52,7 @@ param(
|
|||||||
[string] $VmrunPath = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe',
|
[string] $VmrunPath = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe',
|
||||||
|
|
||||||
# Skip ICMP ping phase (phase 2). Useful when the guest firewall blocks ICMP
|
# Skip ICMP ping phase (phase 2). Useful when the guest firewall blocks ICMP
|
||||||
# but WinRM (TCP 5985) is open. Phase 1 (vmrun state) and phase 3 (WinRM)
|
# but WinRM (TCP 5986) is open. Phase 1 (vmrun state) and phase 3 (WinRM)
|
||||||
# are still checked.
|
# are still checked.
|
||||||
[switch] $SkipPing
|
[switch] $SkipPing
|
||||||
)
|
)
|
||||||
@@ -68,6 +68,7 @@ $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)..."
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ while ((Get-Date) -lt $deadline) {
|
|||||||
|
|
||||||
# ── Phase 3: WinRM responsive ────────────────────────────────────────
|
# ── Phase 3: WinRM responsive ────────────────────────────────────────
|
||||||
try {
|
try {
|
||||||
Test-WSMan -ComputerName $IPAddress -ErrorAction Stop | Out-Null
|
Test-WSMan -ComputerName $IPAddress -Port 5986 -UseSSL -SessionOption $wsmOpt -ErrorAction Stop | Out-Null
|
||||||
# Success — VM is ready
|
# Success — VM is ready
|
||||||
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
|
||||||
@@ -117,7 +118,7 @@ while ((Get-Date) -lt $deadline) {
|
|||||||
catch {
|
catch {
|
||||||
$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 5985..."
|
Write-Host "[Wait-VMReady] Phase 3/3: ping OK, waiting for WinRM on port 5986 (HTTPS)..."
|
||||||
$lastPhase = $phase
|
$lastPhase = $phase
|
||||||
}
|
}
|
||||||
Start-Sleep -Seconds $PollIntervalSeconds
|
Start-Sleep -Seconds $PollIntervalSeconds
|
||||||
|
|||||||
@@ -43,8 +43,8 @@
|
|||||||
them after applying updates (§7.2 Strategy B)
|
them after applying updates (§7.2 Strategy B)
|
||||||
- Windows Firewall all profiles disabled (VMnet8 NAT lab; Setup Step 1 validates)
|
- 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
|
- RDP enabled (no NLA, lab only) + firewall rules for RDP/ICMP/WinRM-HTTPS
|
||||||
- WinRM HTTP 5985 + HTTPS 5986 (self-signed cert), Basic auth,
|
- WinRM HTTPS 5986 only (self-signed cert), Basic auth, AllowUnencrypted=false,
|
||||||
AllowUnencrypted, TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25
|
TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25
|
||||||
- ICMPv4 inbound allowed
|
- ICMPv4 inbound allowed
|
||||||
- Lock screen disabled, Server Manager (policy + HKLM + task + HKCU + Default hive),
|
- Lock screen disabled, Server Manager (policy + HKLM + task + HKCU + Default hive),
|
||||||
DisableCAD (Policies\System + Winlogon), OOBE/telemetry suppressed;
|
DisableCAD (Policies\System + Winlogon), OOBE/telemetry suppressed;
|
||||||
@@ -617,17 +617,17 @@ Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' -ErrorAction SilentlyConti
|
|||||||
New-NetFirewallRule -DisplayName 'ICMPv4-In' -Protocol ICMPv4 -IcmpType 8 -Direction Inbound -Action Allow -Profile Any -ErrorAction SilentlyContinue | Out-Null
|
New-NetFirewallRule -DisplayName 'ICMPv4-In' -Protocol ICMPv4 -IcmpType 8 -Direction Inbound -Action Allow -Profile Any -ErrorAction SilentlyContinue | Out-Null
|
||||||
L 'RDP + ICMP allowed'
|
L 'RDP + ICMP allowed'
|
||||||
|
|
||||||
# ── WinRM HTTP + HTTPS, Basic+Unencrypted, TrustedHosts=* (lab only) ─────
|
# ── WinRM HTTPS-only/5986, Basic auth over HTTPS, TrustedHosts=* (lab only) ─
|
||||||
|
# AllowUnencrypted intentionally left false — all WinRM traffic uses HTTPS/5986.
|
||||||
Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null
|
Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null
|
||||||
winrm set winrm/config/service '@{AllowUnencrypted="true"}' | Out-Null
|
|
||||||
winrm set winrm/config/service/auth '@{Basic="true"}' | Out-Null
|
winrm set winrm/config/service/auth '@{Basic="true"}' | Out-Null
|
||||||
winrm set winrm/config/client/auth '@{Basic="true"}' | Out-Null
|
winrm set winrm/config/client/auth '@{Basic="true"}' | Out-Null
|
||||||
winrm set winrm/config/client '@{TrustedHosts="*"}' | Out-Null
|
winrm set winrm/config/client '@{TrustedHosts="*"}' | Out-Null
|
||||||
try {
|
try {
|
||||||
`$cert = New-SelfSignedCertificate -DnsName `$env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My
|
`$cert = New-SelfSignedCertificate -DnsName `$env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My
|
||||||
New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * -CertificateThumbprint `$cert.Thumbprint -Force | Out-Null
|
New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * -CertificateThumbprint `$cert.Thumbprint -Force | Out-Null
|
||||||
New-NetFirewallRule -DisplayName 'WinRM-HTTPS' -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Profile Any | Out-Null
|
New-NetFirewallRule -DisplayName 'WinRM-HTTPS' -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Profile Any -RemoteAddress '192.168.79.0/24' | Out-Null
|
||||||
L 'WinRM HTTPS listener configured'
|
L 'WinRM HTTPS listener configured (port 5986, restricted to 192.168.79.0/24)'
|
||||||
} catch { L "WinRM HTTPS failed: `$(`$_.Exception.Message)" }
|
} catch { L "WinRM HTTPS failed: `$(`$_.Exception.Message)" }
|
||||||
winrm set winrm/config/winrs '@{MaxProcessesPerShell="25"}' | Out-Null
|
winrm set winrm/config/winrs '@{MaxProcessesPerShell="25"}' | Out-Null
|
||||||
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048 -Force 3>`$null
|
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048 -Force 3>`$null
|
||||||
@@ -1107,8 +1107,7 @@ Write-Host " Admin user : $AdminUser"
|
|||||||
Write-Host " Snapshot : $SnapshotName"
|
Write-Host " Snapshot : $SnapshotName"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host " RDP : mstsc /v:$guestIP"
|
Write-Host " RDP : mstsc /v:$guestIP"
|
||||||
Write-Host " WinRM HTTP : 5985"
|
Write-Host " WinRM HTTPS : 5986 (self-signed, AllowUnencrypted=false)"
|
||||||
Write-Host " WinRM HTTPS : 5986 (self-signed)"
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host " Next steps (from template\ dir):"
|
Write-Host " Next steps (from template\ dir):"
|
||||||
Write-Host " .\Validate-DeployState.ps1 -VMIPAddress $guestIP"
|
Write-Host " .\Validate-DeployState.ps1 -VMIPAddress $guestIP"
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Automates the template VM provisioning from the host machine:
|
Automates the template VM provisioning from the host machine:
|
||||||
1. Pre-flight validation: script presence, IP octet range, TCP/5985 reachable
|
1. Pre-flight validation: script presence, IP octet range, TCP/5986 reachable
|
||||||
2. Configures host WinRM client (AllowUnencrypted, TrustedHosts) — restored on exit
|
2. Configures host WinRM client (TrustedHosts only, HTTPS needs no AllowUnencrypted) — restored on exit
|
||||||
3. Validates host WinRM client settings applied correctly
|
3. Validates host WinRM TrustedHosts applied correctly
|
||||||
4. Tests WinRM connectivity (Test-WSMan) — fails fast with actionable message
|
4. Tests WinRM HTTPS connectivity (Test-WSMan -UseSSL -Port 5986) — fails fast with actionable message
|
||||||
5. Opens PSSession to the VM
|
5. Opens PSSession to the VM
|
||||||
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
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
9. Handles the reboot-required case (exit 3010) and optionally re-runs
|
9. Handles the reboot-required case (exit 3010) and optionally re-runs
|
||||||
10. Post-setup remote validation: 9 guest-state checks via a single Invoke-Command
|
10. Post-setup remote validation: 9 guest-state checks via a single Invoke-Command
|
||||||
(WinRM, user+admin, UAC, firewall, .NET, Python, MSBuild, CI dirs)
|
(WinRM, user+admin, UAC, firewall, .NET, Python, MSBuild, CI dirs)
|
||||||
11. Restores host WinRM client settings in finally block
|
11. Restores host TrustedHosts in finally block
|
||||||
|
|
||||||
Every validation step uses Assert-Step: prints [OK] on success, throws on failure.
|
Every validation step uses Assert-Step: prints [OK] on success, throws on failure.
|
||||||
The snapshot should only be taken after this script exits 0.
|
The snapshot should only be taken after this script exits 0.
|
||||||
@@ -227,18 +227,18 @@ Assert-Step 'PreFlight' "VMIPAddress octets in 0..255" {
|
|||||||
|
|
||||||
# WinRM readiness — retry loop (longer timeout if VM was just powered on by this script)
|
# WinRM readiness — retry loop (longer timeout if VM was just powered on by this script)
|
||||||
$winrmTimeoutSec = if ($vmWasPoweredOn) { 180 } else { 20 }
|
$winrmTimeoutSec = if ($vmWasPoweredOn) { 180 } else { 20 }
|
||||||
Write-Host "[Prepare] Waiting for WinRM on $VMIPAddress`:5985 (timeout ${winrmTimeoutSec}s)..."
|
Write-Host "[Prepare] Waiting for WinRM on $VMIPAddress`:5986 (timeout ${winrmTimeoutSec}s)..."
|
||||||
$winrmDeadline = [datetime]::UtcNow.AddSeconds($winrmTimeoutSec)
|
$winrmDeadline = [datetime]::UtcNow.AddSeconds($winrmTimeoutSec)
|
||||||
$winrmReady = $false
|
$winrmReady = $false
|
||||||
while ([datetime]::UtcNow -lt $winrmDeadline) {
|
while ([datetime]::UtcNow -lt $winrmDeadline) {
|
||||||
if (Test-NetConnection -ComputerName $VMIPAddress -Port 5985 `
|
if (Test-NetConnection -ComputerName $VMIPAddress -Port 5986 `
|
||||||
-InformationLevel Quiet -WarningAction SilentlyContinue -ErrorAction SilentlyContinue) {
|
-InformationLevel Quiet -WarningAction SilentlyContinue -ErrorAction SilentlyContinue) {
|
||||||
$winrmReady = $true; break
|
$winrmReady = $true; break
|
||||||
}
|
}
|
||||||
Start-Sleep -Seconds 5
|
Start-Sleep -Seconds 5
|
||||||
Write-Host " ... waiting for WinRM..."
|
Write-Host " ... waiting for WinRM..."
|
||||||
}
|
}
|
||||||
Assert-Step 'PreFlight' "VMIPAddress $VMIPAddress reachable on TCP/5985" { $winrmReady }
|
Assert-Step 'PreFlight' "VMIPAddress $VMIPAddress reachable on TCP/5986" { $winrmReady }
|
||||||
|
|
||||||
# Prompt for BuildPassword if not supplied — never use a hardcoded default.
|
# Prompt for BuildPassword if not supplied — never use a hardcoded default.
|
||||||
if ($BuildPassword -eq '') {
|
if ($BuildPassword -eq '') {
|
||||||
@@ -260,60 +260,50 @@ else {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
||||||
|
$wsmOpt = New-WSManSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
||||||
|
|
||||||
# ── Configure HOST-side WinRM client (required for Basic auth over HTTP) ──────
|
# ── Configure HOST-side WinRM client (TrustedHosts for HTTPS Basic auth) ──────
|
||||||
# AllowUnencrypted is needed for HTTP/5985 Basic auth. We save the prior value
|
# HTTPS/5986 does not require AllowUnencrypted on the host side. Only TrustedHosts
|
||||||
# and restore it in the finally block so this script doesn't permanently change
|
# needs appending so PS accepts the non-domain VM. Restored in the finally block.
|
||||||
# the host security posture.
|
|
||||||
Write-Host "[Prepare] Configuring host WinRM client for unencrypted Basic auth (will be restored on exit)..."
|
|
||||||
$prevAllowUnencrypted = $null
|
|
||||||
$prevTrustedHosts = $null
|
$prevTrustedHosts = $null
|
||||||
try {
|
try {
|
||||||
$prevAllowUnencrypted = (Get-Item WSMan:\localhost\Client\AllowUnencrypted -ErrorAction Stop).Value
|
|
||||||
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction Stop).Value
|
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction Stop).Value
|
||||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force -ErrorAction Stop
|
|
||||||
# Add VM IP to TrustedHosts if not already present
|
# Add VM IP to TrustedHosts if not already present
|
||||||
if ($prevTrustedHosts -ne '*' -and $prevTrustedHosts -notlike "*$VMIPAddress*") {
|
if ($prevTrustedHosts -ne '*' -and $prevTrustedHosts -notlike "*$VMIPAddress*") {
|
||||||
$newTrusted = if ($prevTrustedHosts -eq '') { $VMIPAddress } else { "$prevTrustedHosts,$VMIPAddress" }
|
$newTrusted = if ($prevTrustedHosts -eq '') { $VMIPAddress } else { "$prevTrustedHosts,$VMIPAddress" }
|
||||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $newTrusted -Force -ErrorAction Stop
|
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $newTrusted -Force -ErrorAction Stop
|
||||||
}
|
}
|
||||||
Write-Host "[Prepare] Host WinRM client configured."
|
Write-Host "[Prepare] Host TrustedHosts configured."
|
||||||
# Validation
|
|
||||||
Assert-Step 'HostWinRM' 'Client/AllowUnencrypted=true' {
|
|
||||||
(Get-Item WSMan:\localhost\Client\AllowUnencrypted).Value -eq 'true'
|
|
||||||
}
|
|
||||||
Assert-Step 'HostWinRM' "TrustedHosts includes $VMIPAddress (or '*')" {
|
Assert-Step 'HostWinRM' "TrustedHosts includes $VMIPAddress (or '*')" {
|
||||||
$th = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
$th = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||||
$th -eq '*' -or $th -like "*$VMIPAddress*"
|
$th -eq '*' -or $th -like "*$VMIPAddress*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Warning "[Prepare] Could not configure host WinRM client settings (need elevation?)."
|
Write-Warning "[Prepare] Could not configure host WinRM TrustedHosts (need elevation?)."
|
||||||
Write-Warning "Run once in an elevated PowerShell on the HOST:"
|
Write-Warning "Run once in an elevated PowerShell on the HOST:"
|
||||||
Write-Warning " Set-Item WSMan:\localhost\Client\AllowUnencrypted `$true -Force"
|
|
||||||
Write-Warning " Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force"
|
Write-Warning " Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force"
|
||||||
Write-Warning "Then re-run this script."
|
Write-Warning "Then re-run this script."
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Write-Host "[Prepare] Testing WinRM connectivity to $VMIPAddress..."
|
Write-Host "[Prepare] Testing WinRM HTTPS connectivity to $VMIPAddress`:5986..."
|
||||||
try {
|
try {
|
||||||
Test-WSMan -ComputerName $VMIPAddress -Credential $credential `
|
Test-WSMan -ComputerName $VMIPAddress -Port 5986 -UseSSL `
|
||||||
|
-SessionOption $wsmOpt -Credential $credential `
|
||||||
-Authentication Basic -ErrorAction Stop | Out-Null
|
-Authentication Basic -ErrorAction Stop | Out-Null
|
||||||
Write-Host "[Prepare] WinRM reachable."
|
Write-Host "[Prepare] WinRM HTTPS reachable."
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Error @"
|
Write-Error @"
|
||||||
[Prepare] Cannot reach $VMIPAddress via WinRM.
|
[Prepare] Cannot reach $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
|
||||||
- WinRM is enabled inside the VM (run as Administrator in VM):
|
- WinRM HTTPS is enabled inside the VM — Deploy-WinBuild2025.ps1 post-install.ps1
|
||||||
winrm quickconfig -q
|
must have run successfully (creates self-signed cert + HTTPS listener on 5986)
|
||||||
Enable-PSRemoting -Force -SkipNetworkProfileCheck
|
- Windows Firewall allows port 5986 inbound (or firewall is disabled on the guest)
|
||||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
|
|
||||||
- Windows Firewall allows port 5985 inbound
|
|
||||||
- The IP is correct (check ipconfig inside the VM)
|
- The IP is correct (check ipconfig inside the VM)
|
||||||
|
|
||||||
Error: $_
|
Error: $_
|
||||||
@@ -327,6 +317,8 @@ $session = New-PSSession `
|
|||||||
-ComputerName $VMIPAddress `
|
-ComputerName $VMIPAddress `
|
||||||
-Credential $credential `
|
-Credential $credential `
|
||||||
-SessionOption $sessionOptions `
|
-SessionOption $sessionOptions `
|
||||||
|
-UseSSL `
|
||||||
|
-Port 5986 `
|
||||||
-Authentication Basic `
|
-Authentication Basic `
|
||||||
-ErrorAction Stop
|
-ErrorAction Stop
|
||||||
|
|
||||||
@@ -423,7 +415,9 @@ try {
|
|||||||
while ((Get-Date) -lt $deadline) {
|
while ((Get-Date) -lt $deadline) {
|
||||||
Start-Sleep -Seconds 10
|
Start-Sleep -Seconds 10
|
||||||
try {
|
try {
|
||||||
$r = Test-WSMan -ComputerName $IP -Credential $Cred `
|
$wsm = New-WSManSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
||||||
|
$r = Test-WSMan -ComputerName $IP -Port 5986 -UseSSL `
|
||||||
|
-SessionOption $wsm -Credential $Cred `
|
||||||
-Authentication Basic -ErrorAction Stop
|
-Authentication Basic -ErrorAction Stop
|
||||||
if ($r) { return $true }
|
if ($r) { return $true }
|
||||||
} catch { }
|
} catch { }
|
||||||
@@ -481,7 +475,8 @@ try {
|
|||||||
}
|
}
|
||||||
Write-Host "[Prepare] Guest WinRM ready, reopening session..."
|
Write-Host "[Prepare] Guest WinRM ready, reopening session..."
|
||||||
$session = New-PSSession -ComputerName $VMIPAddress -Credential $credential `
|
$session = New-PSSession -ComputerName $VMIPAddress -Credential $credential `
|
||||||
-SessionOption $sessionOptions -Authentication Basic -ErrorAction Stop
|
-SessionOption $sessionOptions -UseSSL -Port 5986 `
|
||||||
|
-Authentication Basic -ErrorAction Stop
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Post-setup remote validation ──────────────────────────────────────────
|
# ── Post-setup remote validation ──────────────────────────────────────────
|
||||||
@@ -670,12 +665,9 @@ try {
|
|||||||
finally {
|
finally {
|
||||||
Remove-PSSession $session -ErrorAction SilentlyContinue
|
Remove-PSSession $session -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
# Restore host WinRM client settings to their original values
|
# Restore host TrustedHosts to its original value
|
||||||
if ($null -ne $prevAllowUnencrypted) {
|
|
||||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $prevAllowUnencrypted -Force -ErrorAction SilentlyContinue
|
|
||||||
}
|
|
||||||
if ($null -ne $prevTrustedHosts) {
|
if ($null -ne $prevTrustedHosts) {
|
||||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $prevTrustedHosts -Force -ErrorAction SilentlyContinue
|
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $prevTrustedHosts -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
Write-Host "[Prepare] Host WinRM client settings restored."
|
Write-Host "[Prepare] Host TrustedHosts restored."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
Validation: Domain/Private/Public all Enabled=False
|
Validation: Domain/Private/Public all Enabled=False
|
||||||
Step 2 — Defender: validation only — Deploy set DisableAntiSpyware=1 GPO + RTP off.
|
Step 2 — Defender: validation only — Deploy set DisableAntiSpyware=1 GPO + RTP off.
|
||||||
Validation: GPO DisableAntiSpyware=1
|
Validation: GPO DisableAntiSpyware=1
|
||||||
Step 3 — WinRM: validation only — Deploy ran Enable-PSRemoting, Basic auth,
|
Step 3 — WinRM: validation only — Deploy ran Enable-PSRemoting, Basic auth over HTTPS,
|
||||||
AllowUnencrypted, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25.
|
AllowUnencrypted=false (HTTPS-only), MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25.
|
||||||
Validation: service Running+Automatic, AllowUnencrypted, Auth/Basic, memory
|
Validation: service Running+Automatic, AllowUnencrypted=false, Auth/Basic, memory
|
||||||
Step 4 — Local build user account ($BuildUsername, PasswordNeverExpires, Administrators)
|
Step 4 — Local build user account ($BuildUsername, PasswordNeverExpires, Administrators)
|
||||||
Validation: user exists, enabled, PasswordNeverExpires, admin membership
|
Validation: user exists, enabled, PasswordNeverExpires, admin membership
|
||||||
Step 4b — UAC: validation only — Deploy set EnableLUA=0, LocalAccountTokenFilterPolicy=1.
|
Step 4b — UAC: validation only — Deploy set EnableLUA=0, LocalAccountTokenFilterPolicy=1.
|
||||||
@@ -161,6 +161,44 @@ function Assert-Step {
|
|||||||
Write-Host " [OK] $Description" -ForegroundColor Green
|
Write-Host " [OK] $Description" -ForegroundColor Green
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Assert-Hash {
|
||||||
|
# Verifies SHA256 of a downloaded file against a pinned expected value.
|
||||||
|
# If $Expected is empty the check is skipped with a warning (unpinned installer).
|
||||||
|
# To pin: download the file, run (Get-FileHash <path> -Algorithm SHA256).Hash, fill in below.
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)] [string] $FilePath,
|
||||||
|
[Parameter(Mandatory)] [string] $Label,
|
||||||
|
[string] $Expected = ''
|
||||||
|
)
|
||||||
|
if (-not $Expected) {
|
||||||
|
Write-Host " [WARN] Hash not pinned for $Label — set `$script:Hashes to enforce (see §1.3)" -ForegroundColor Yellow
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$actual = (Get-FileHash $FilePath -Algorithm SHA256).Hash.ToUpper()
|
||||||
|
if ($actual -ne $Expected.ToUpper()) {
|
||||||
|
throw "Hash mismatch for ${Label}:`n expected: $($Expected.ToUpper())`n actual: $actual`nPossible MITM or wrong installer version — do NOT proceed."
|
||||||
|
}
|
||||||
|
Write-Host " [OK] SHA256 verified: $Label" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Pinned SHA256 hashes for downloaded installers (§1.3) ─────────────────────
|
||||||
|
# Fill in the hash values below. Get them by:
|
||||||
|
# (Invoke-WebRequest '<URL>' -UseBasicParsing -OutFile 'tmp.bin'; (Get-FileHash 'tmp.bin' -Algorithm SHA256).Hash)
|
||||||
|
# Update when the corresponding version param changes. Leave '' to skip (warns at runtime).
|
||||||
|
$script:Hashes = @{
|
||||||
|
# python-<PythonVersion>-amd64.exe from https://www.python.org/downloads/release/python-XYZ/
|
||||||
|
# Update $script:Hashes['Python'] when $PythonVersion changes.
|
||||||
|
'Python' = ''
|
||||||
|
|
||||||
|
# dotnet-install.ps1 from https://dot.net/v1/dotnet-install.ps1
|
||||||
|
# Changes with each .NET SDK release cycle — verify after any dotnet version bump.
|
||||||
|
'DotNetInstallScript' = ''
|
||||||
|
|
||||||
|
# vs_buildtools.exe bootstrapper from aka.ms/vs/stable/vs_buildtools.exe
|
||||||
|
# Stable within a VS release cycle — update when VS major version changes.
|
||||||
|
'VSBuildToolsBootstrapper' = ''
|
||||||
|
}
|
||||||
|
|
||||||
# ── Pre-flight: remove temp files from any previous partial run ───────────────
|
# ── Pre-flight: remove temp files from any previous partial run ───────────────
|
||||||
# These files are normally deleted at the end of each step that creates them.
|
# These files are normally deleted at the end of each step that creates them.
|
||||||
# A previous interrupted run may have left them behind — remove before starting.
|
# A previous interrupted run may have left them behind — remove before starting.
|
||||||
@@ -203,9 +241,9 @@ Assert-Step 'Defender' 'GPO DisableAntiSpyware=1 (set by Deploy-WinBuild2025)' {
|
|||||||
# ── Step 3: WinRM (validation only — configured by Deploy-WinBuild2025) ───────
|
# ── Step 3: WinRM (validation only — configured by Deploy-WinBuild2025) ───────
|
||||||
Write-Step "WinRM state (validation only — configured at deploy time)"
|
Write-Step "WinRM state (validation only — configured at deploy time)"
|
||||||
|
|
||||||
# Deploy-WinBuild2025.ps1 post-install.ps1 ran Enable-PSRemoting, set Basic auth,
|
# Deploy-WinBuild2025.ps1 post-install.ps1 ran Enable-PSRemoting, set Basic auth over HTTPS,
|
||||||
# AllowUnencrypted, TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25,
|
# AllowUnencrypted=false (HTTPS/5986 only), TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h,
|
||||||
# and StartupType=Automatic. Validated here before the build user and toolchain steps.
|
# MaxProcesses=25, StartupType=Automatic. Validated here before the build user and toolchain steps.
|
||||||
|
|
||||||
Assert-Step 'WinRM' 'service Running' {
|
Assert-Step 'WinRM' 'service Running' {
|
||||||
(Get-Service WinRM -ErrorAction Stop).Status -eq 'Running'
|
(Get-Service WinRM -ErrorAction Stop).Status -eq 'Running'
|
||||||
@@ -213,8 +251,8 @@ Assert-Step 'WinRM' 'service Running' {
|
|||||||
Assert-Step 'WinRM' 'service StartType Automatic' {
|
Assert-Step 'WinRM' 'service StartType Automatic' {
|
||||||
(Get-Service WinRM -ErrorAction Stop).StartType -eq 'Automatic'
|
(Get-Service WinRM -ErrorAction Stop).StartType -eq 'Automatic'
|
||||||
}
|
}
|
||||||
Assert-Step 'WinRM' 'AllowUnencrypted=true' {
|
Assert-Step 'WinRM' 'AllowUnencrypted=false (HTTPS-only)' {
|
||||||
(Get-Item WSMan:\localhost\Service\AllowUnencrypted).Value -eq 'true'
|
(Get-Item WSMan:\localhost\Service\AllowUnencrypted).Value -eq 'false'
|
||||||
}
|
}
|
||||||
Assert-Step 'WinRM' 'Auth/Basic=true' {
|
Assert-Step 'WinRM' 'Auth/Basic=true' {
|
||||||
(Get-Item WSMan:\localhost\Service\Auth\Basic).Value -eq 'true'
|
(Get-Item WSMan:\localhost\Service\Auth\Basic).Value -eq 'true'
|
||||||
@@ -589,6 +627,8 @@ else {
|
|||||||
$dotnetInstallScript = "$env:TEMP\dotnet-install.ps1"
|
$dotnetInstallScript = "$env:TEMP\dotnet-install.ps1"
|
||||||
Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' `
|
Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' `
|
||||||
-OutFile $dotnetInstallScript -UseBasicParsing
|
-OutFile $dotnetInstallScript -UseBasicParsing
|
||||||
|
Assert-Hash -FilePath $dotnetInstallScript -Label 'dotnet-install.ps1' `
|
||||||
|
-Expected $script:Hashes['DotNetInstallScript']
|
||||||
|
|
||||||
Write-Host "Installing .NET SDK $DotNetSdkVersion (channel)..."
|
Write-Host "Installing .NET SDK $DotNetSdkVersion (channel)..."
|
||||||
& $dotnetInstallScript `
|
& $dotnetInstallScript `
|
||||||
@@ -635,6 +675,8 @@ else {
|
|||||||
$pyInstallerPath = 'C:\CI\python_installer.exe'
|
$pyInstallerPath = 'C:\CI\python_installer.exe'
|
||||||
Write-Host "Downloading Python $PythonVersion installer..."
|
Write-Host "Downloading Python $PythonVersion installer..."
|
||||||
Invoke-WebRequest -Uri $pyInstallerUrl -OutFile $pyInstallerPath -UseBasicParsing
|
Invoke-WebRequest -Uri $pyInstallerUrl -OutFile $pyInstallerPath -UseBasicParsing
|
||||||
|
Assert-Hash -FilePath $pyInstallerPath -Label "python-$PythonVersion-amd64.exe" `
|
||||||
|
-Expected $script:Hashes['Python']
|
||||||
|
|
||||||
Write-Host "Installing Python $PythonVersion (all users, prepend PATH)..."
|
Write-Host "Installing Python $PythonVersion (all users, prepend PATH)..."
|
||||||
$pyProc = Start-Process -FilePath $pyInstallerPath -ArgumentList @(
|
$pyProc = Start-Process -FilePath $pyInstallerPath -ArgumentList @(
|
||||||
@@ -689,6 +731,8 @@ else {
|
|||||||
Write-Host "Downloading VS Build Tools installer..."
|
Write-Host "Downloading VS Build Tools installer..."
|
||||||
Remove-Item $vsInstallerPath -ErrorAction SilentlyContinue # remove any stale/corrupt copy
|
Remove-Item $vsInstallerPath -ErrorAction SilentlyContinue # remove any stale/corrupt copy
|
||||||
Invoke-WebRequest -Uri $vsInstallerUrl -OutFile $vsInstallerPath -UseBasicParsing
|
Invoke-WebRequest -Uri $vsInstallerUrl -OutFile $vsInstallerPath -UseBasicParsing
|
||||||
|
Assert-Hash -FilePath $vsInstallerPath -Label 'vs_buildtools.exe' `
|
||||||
|
-Expected $script:Hashes['VSBuildToolsBootstrapper']
|
||||||
|
|
||||||
# Remove Zone.Identifier ADS — files downloaded from internet are marked Zone 3 (Internet).
|
# Remove Zone.Identifier ADS — files downloaded from internet are marked Zone 3 (Internet).
|
||||||
# SYSTEM account cannot execute them without this unblock step.
|
# SYSTEM account cannot execute them without this unblock step.
|
||||||
|
|||||||
@@ -42,11 +42,9 @@ $cred = New-Object System.Management.Automation.PSCredential(
|
|||||||
)
|
)
|
||||||
$so = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
$so = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
||||||
|
|
||||||
$prevAllowUnenc = (Get-Item WSMan:\localhost\Client\AllowUnencrypted).Value
|
|
||||||
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force
|
|
||||||
$cur = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
$cur = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||||
if ($cur -ne '*' -and $cur -notmatch [regex]::Escape($VMIPAddress)) {
|
if ($cur -ne '*' -and $cur -notmatch [regex]::Escape($VMIPAddress)) {
|
||||||
$newHosts = if ($cur) { "$cur,$VMIPAddress" } else { $VMIPAddress }
|
$newHosts = if ($cur) { "$cur,$VMIPAddress" } else { $VMIPAddress }
|
||||||
@@ -56,7 +54,7 @@ try {
|
|||||||
Write-Host "`nValidating Deploy state on $VMIPAddress..." -ForegroundColor Cyan
|
Write-Host "`nValidating Deploy state on $VMIPAddress..." -ForegroundColor Cyan
|
||||||
|
|
||||||
$checks = Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
$checks = Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
||||||
-Authentication Basic -SessionOption $so -ScriptBlock {
|
-Authentication Basic -UseSSL -Port 5986 -SessionOption $so -ScriptBlock {
|
||||||
|
|
||||||
$results = [System.Collections.Generic.List[PSCustomObject]]::new()
|
$results = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||||
|
|
||||||
@@ -82,8 +80,8 @@ try {
|
|||||||
# ── WinRM ─────────────────────────────────────────────────────────────
|
# ── WinRM ─────────────────────────────────────────────────────────────
|
||||||
Chk 'WinRM service Running' { (Get-Service WinRM -EA Stop).Status -eq 'Running' }
|
Chk 'WinRM service Running' { (Get-Service WinRM -EA Stop).Status -eq 'Running' }
|
||||||
Chk 'WinRM service Automatic' { (Get-Service WinRM -EA Stop).StartType -eq 'Automatic' }
|
Chk 'WinRM service Automatic' { (Get-Service WinRM -EA Stop).StartType -eq 'Automatic' }
|
||||||
Chk 'WinRM AllowUnencrypted=true' {
|
Chk 'WinRM AllowUnencrypted=false (HTTPS-only)' {
|
||||||
(Get-Item WSMan:\localhost\Service\AllowUnencrypted -EA Stop).Value -eq 'true'
|
(Get-Item WSMan:\localhost\Service\AllowUnencrypted -EA Stop).Value -eq 'false'
|
||||||
}
|
}
|
||||||
Chk 'WinRM Auth/Basic=true' {
|
Chk 'WinRM Auth/Basic=true' {
|
||||||
(Get-Item WSMan:\localhost\Service\Auth\Basic -EA Stop).Value -eq 'true'
|
(Get-Item WSMan:\localhost\Service\Auth\Basic -EA Stop).Value -eq 'true'
|
||||||
@@ -182,6 +180,5 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $prevAllowUnenc -Force -EA SilentlyContinue
|
|
||||||
Set-Item WSMan:\localhost\Client\TrustedHosts $prevTrustedHosts -Force -EA SilentlyContinue
|
Set-Item WSMan:\localhost\Client\TrustedHosts $prevTrustedHosts -Force -EA SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,11 +63,9 @@ $cred = New-Object System.Management.Automation.PSCredential(
|
|||||||
)
|
)
|
||||||
$so = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
$so = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
||||||
|
|
||||||
$prevAllowUnenc = (Get-Item WSMan:\localhost\Client\AllowUnencrypted).Value
|
|
||||||
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force
|
|
||||||
$cur = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
$cur = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||||
if ($cur -ne '*' -and $cur -notmatch [regex]::Escape($VMIPAddress)) {
|
if ($cur -ne '*' -and $cur -notmatch [regex]::Escape($VMIPAddress)) {
|
||||||
$newHosts = if ($cur) { "$cur,$VMIPAddress" } else { $VMIPAddress }
|
$newHosts = if ($cur) { "$cur,$VMIPAddress" } else { $VMIPAddress }
|
||||||
@@ -77,7 +75,7 @@ try {
|
|||||||
Write-Host "`nValidating full Setup state on $VMIPAddress..." -ForegroundColor Cyan
|
Write-Host "`nValidating full Setup state on $VMIPAddress..." -ForegroundColor Cyan
|
||||||
|
|
||||||
$checks = Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
$checks = Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
||||||
-Authentication Basic -SessionOption $so -ScriptBlock {
|
-Authentication Basic -UseSSL -Port 5986 -SessionOption $so -ScriptBlock {
|
||||||
|
|
||||||
param($BuildUsername, $DotNetChannel, $PythonVersion)
|
param($BuildUsername, $DotNetChannel, $PythonVersion)
|
||||||
|
|
||||||
@@ -109,8 +107,8 @@ try {
|
|||||||
# WinRM
|
# WinRM
|
||||||
Chk 'WinRM service Running' { (Get-Service WinRM -EA Stop).Status -eq 'Running' }
|
Chk 'WinRM service Running' { (Get-Service WinRM -EA Stop).Status -eq 'Running' }
|
||||||
Chk 'WinRM service Automatic' { (Get-Service WinRM -EA Stop).StartType -eq 'Automatic' }
|
Chk 'WinRM service Automatic' { (Get-Service WinRM -EA Stop).StartType -eq 'Automatic' }
|
||||||
Chk 'WinRM AllowUnencrypted=true' {
|
Chk 'WinRM AllowUnencrypted=false (HTTPS-only)' {
|
||||||
(Get-Item WSMan:\localhost\Service\AllowUnencrypted -EA Stop).Value -eq 'true'
|
(Get-Item WSMan:\localhost\Service\AllowUnencrypted -EA Stop).Value -eq 'false'
|
||||||
}
|
}
|
||||||
Chk 'WinRM Auth/Basic=true' {
|
Chk 'WinRM Auth/Basic=true' {
|
||||||
(Get-Item WSMan:\localhost\Service\Auth\Basic -EA Stop).Value -eq 'true'
|
(Get-Item WSMan:\localhost\Service\Auth\Basic -EA Stop).Value -eq 'true'
|
||||||
@@ -270,7 +268,7 @@ try {
|
|||||||
Write-Host "Applying remediation for $($failedNames.Count) failed check(s)..." -ForegroundColor Yellow
|
Write-Host "Applying remediation for $($failedNames.Count) failed check(s)..." -ForegroundColor Yellow
|
||||||
|
|
||||||
Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
||||||
-Authentication Basic -SessionOption $so -ScriptBlock {
|
-Authentication Basic -UseSSL -Port 5986 -SessionOption $so -ScriptBlock {
|
||||||
|
|
||||||
param([string[]] $FailedNames, [string] $AdminPassword)
|
param([string[]] $FailedNames, [string] $AdminPassword)
|
||||||
|
|
||||||
@@ -296,7 +294,7 @@ try {
|
|||||||
# ── Re-run checks ─────────────────────────────────────────────────────────
|
# ── Re-run checks ─────────────────────────────────────────────────────────
|
||||||
Write-Host "`nRe-validating after remediation..." -ForegroundColor Cyan
|
Write-Host "`nRe-validating after remediation..." -ForegroundColor Cyan
|
||||||
$checks = Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
$checks = Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
||||||
-Authentication Basic -SessionOption $so -ScriptBlock {
|
-Authentication Basic -UseSSL -Port 5986 -SessionOption $so -ScriptBlock {
|
||||||
|
|
||||||
param($BuildUsername, $DotNetChannel, $PythonVersion)
|
param($BuildUsername, $DotNetChannel, $PythonVersion)
|
||||||
$results = [System.Collections.Generic.List[PSCustomObject]]::new()
|
$results = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||||
@@ -310,7 +308,7 @@ try {
|
|||||||
Chk 'Defender GPO DisableAntiSpyware=1' { (Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender' -Name DisableAntiSpyware -EA Stop).DisableAntiSpyware -eq 1 }
|
Chk 'Defender GPO DisableAntiSpyware=1' { (Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender' -Name DisableAntiSpyware -EA Stop).DisableAntiSpyware -eq 1 }
|
||||||
Chk 'WinRM service Running' { (Get-Service WinRM -EA Stop).Status -eq 'Running' }
|
Chk 'WinRM service Running' { (Get-Service WinRM -EA Stop).Status -eq 'Running' }
|
||||||
Chk 'WinRM service Automatic' { (Get-Service WinRM -EA Stop).StartType -eq 'Automatic' }
|
Chk 'WinRM service Automatic' { (Get-Service WinRM -EA Stop).StartType -eq 'Automatic' }
|
||||||
Chk 'WinRM AllowUnencrypted=true' { (Get-Item WSMan:\localhost\Service\AllowUnencrypted -EA Stop).Value -eq 'true' }
|
Chk 'WinRM AllowUnencrypted=false (HTTPS-only)' { (Get-Item WSMan:\localhost\Service\AllowUnencrypted -EA Stop).Value -eq 'false' }
|
||||||
Chk 'WinRM Auth/Basic=true' { (Get-Item WSMan:\localhost\Service\Auth\Basic -EA Stop).Value -eq 'true' }
|
Chk 'WinRM Auth/Basic=true' { (Get-Item WSMan:\localhost\Service\Auth\Basic -EA Stop).Value -eq 'true' }
|
||||||
Chk 'WinRM MaxMemoryPerShellMB >= 2048' { [int](Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB -EA Stop).Value -ge 2048 }
|
Chk 'WinRM MaxMemoryPerShellMB >= 2048' { [int](Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB -EA Stop).Value -ge 2048 }
|
||||||
$polSys = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
|
$polSys = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
|
||||||
@@ -369,6 +367,5 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $prevAllowUnenc -Force -EA SilentlyContinue
|
|
||||||
Set-Item WSMan:\localhost\Client\TrustedHosts $prevTrustedHosts -Force -EA SilentlyContinue
|
Set-Item WSMan:\localhost\Client\TrustedHosts $prevTrustedHosts -Force -EA SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user