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:
@@ -43,8 +43,8 @@
|
||||
them after applying updates (§7.2 Strategy B)
|
||||
- Windows Firewall all profiles disabled (VMnet8 NAT lab; Setup Step 1 validates)
|
||||
- RDP enabled (no NLA, lab only) + firewall rules for RDP/ICMP/WinRM-HTTPS
|
||||
- WinRM HTTP 5985 + HTTPS 5986 (self-signed cert), Basic auth,
|
||||
AllowUnencrypted, TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25
|
||||
- WinRM HTTPS 5986 only (self-signed cert), Basic auth, AllowUnencrypted=false,
|
||||
TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25
|
||||
- ICMPv4 inbound allowed
|
||||
- Lock screen disabled, Server Manager (policy + HKLM + task + HKCU + Default hive),
|
||||
DisableCAD (Policies\System + Winlogon), OOBE/telemetry suppressed;
|
||||
@@ -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
|
||||
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
|
||||
winrm set winrm/config/service '@{AllowUnencrypted="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 '@{TrustedHosts="*"}' | Out-Null
|
||||
try {
|
||||
`$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-NetFirewallRule -DisplayName 'WinRM-HTTPS' -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Profile Any | Out-Null
|
||||
L 'WinRM HTTPS listener configured'
|
||||
New-NetFirewallRule -DisplayName 'WinRM-HTTPS' -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Profile Any -RemoteAddress '192.168.79.0/24' | Out-Null
|
||||
L 'WinRM HTTPS listener configured (port 5986, restricted to 192.168.79.0/24)'
|
||||
} catch { L "WinRM HTTPS failed: `$(`$_.Exception.Message)" }
|
||||
winrm set winrm/config/winrs '@{MaxProcessesPerShell="25"}' | Out-Null
|
||||
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 ""
|
||||
Write-Host " RDP : mstsc /v:$guestIP"
|
||||
Write-Host " WinRM HTTP : 5985"
|
||||
Write-Host " WinRM HTTPS : 5986 (self-signed)"
|
||||
Write-Host " WinRM HTTPS : 5986 (self-signed, AllowUnencrypted=false)"
|
||||
Write-Host ""
|
||||
Write-Host " Next steps (from template\ dir):"
|
||||
Write-Host " .\Validate-DeployState.ps1 -VMIPAddress $guestIP"
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
.DESCRIPTION
|
||||
Automates the template VM provisioning from the host machine:
|
||||
1. Pre-flight validation: script presence, IP octet range, TCP/5985 reachable
|
||||
2. Configures host WinRM client (AllowUnencrypted, TrustedHosts) — restored on exit
|
||||
3. Validates host WinRM client settings applied correctly
|
||||
4. Tests WinRM connectivity (Test-WSMan) — fails fast with actionable message
|
||||
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
|
||||
3. Validates host WinRM TrustedHosts applied correctly
|
||||
4. Tests WinRM HTTPS connectivity (Test-WSMan -UseSSL -Port 5986) — fails fast with actionable message
|
||||
5. Opens PSSession to the VM
|
||||
6. Ensures C:\CI exists on guest; validates creation
|
||||
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
|
||||
10. Post-setup remote validation: 9 guest-state checks via a single Invoke-Command
|
||||
(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.
|
||||
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)
|
||||
$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)
|
||||
$winrmReady = $false
|
||||
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) {
|
||||
$winrmReady = $true; break
|
||||
}
|
||||
Start-Sleep -Seconds 5
|
||||
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.
|
||||
if ($BuildPassword -eq '') {
|
||||
@@ -260,60 +260,50 @@ else {
|
||||
}
|
||||
|
||||
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
||||
$wsmOpt = New-WSManSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
||||
|
||||
# ── Configure HOST-side WinRM client (required for Basic auth over HTTP) ──────
|
||||
# AllowUnencrypted is needed for HTTP/5985 Basic auth. We save the prior value
|
||||
# and restore it in the finally block so this script doesn't permanently change
|
||||
# the host security posture.
|
||||
Write-Host "[Prepare] Configuring host WinRM client for unencrypted Basic auth (will be restored on exit)..."
|
||||
$prevAllowUnencrypted = $null
|
||||
$prevTrustedHosts = $null
|
||||
# ── Configure HOST-side WinRM client (TrustedHosts for HTTPS Basic auth) ──────
|
||||
# HTTPS/5986 does not require AllowUnencrypted on the host side. Only TrustedHosts
|
||||
# needs appending so PS accepts the non-domain VM. Restored in the finally block.
|
||||
$prevTrustedHosts = $null
|
||||
try {
|
||||
$prevAllowUnencrypted = (Get-Item WSMan:\localhost\Client\AllowUnencrypted -ErrorAction Stop).Value
|
||||
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction Stop).Value
|
||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force -ErrorAction Stop
|
||||
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction Stop).Value
|
||||
# Add VM IP to TrustedHosts if not already present
|
||||
if ($prevTrustedHosts -ne '*' -and $prevTrustedHosts -notlike "*$VMIPAddress*") {
|
||||
$newTrusted = if ($prevTrustedHosts -eq '') { $VMIPAddress } else { "$prevTrustedHosts,$VMIPAddress" }
|
||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $newTrusted -Force -ErrorAction Stop
|
||||
}
|
||||
Write-Host "[Prepare] Host WinRM client configured."
|
||||
# Validation
|
||||
Assert-Step 'HostWinRM' 'Client/AllowUnencrypted=true' {
|
||||
(Get-Item WSMan:\localhost\Client\AllowUnencrypted).Value -eq 'true'
|
||||
}
|
||||
Write-Host "[Prepare] Host TrustedHosts configured."
|
||||
Assert-Step 'HostWinRM' "TrustedHosts includes $VMIPAddress (or '*')" {
|
||||
$th = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||
$th -eq '*' -or $th -like "*$VMIPAddress*"
|
||||
}
|
||||
}
|
||||
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 " Set-Item WSMan:\localhost\Client\AllowUnencrypted `$true -Force"
|
||||
Write-Warning " Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force"
|
||||
Write-Warning "Then re-run this script."
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
Write-Host "[Prepare] Testing WinRM connectivity to $VMIPAddress..."
|
||||
Write-Host "[Prepare] Testing WinRM HTTPS connectivity to $VMIPAddress`:5986..."
|
||||
try {
|
||||
Test-WSMan -ComputerName $VMIPAddress -Credential $credential `
|
||||
Test-WSMan -ComputerName $VMIPAddress -Port 5986 -UseSSL `
|
||||
-SessionOption $wsmOpt -Credential $credential `
|
||||
-Authentication Basic -ErrorAction Stop | Out-Null
|
||||
Write-Host "[Prepare] WinRM reachable."
|
||||
Write-Host "[Prepare] WinRM HTTPS reachable."
|
||||
}
|
||||
catch {
|
||||
Write-Error @"
|
||||
[Prepare] Cannot reach $VMIPAddress via WinRM.
|
||||
[Prepare] Cannot reach $VMIPAddress via WinRM HTTPS (port 5986).
|
||||
|
||||
Ensure:
|
||||
- The VM is running and on VMnet8 (NAT) with internet access
|
||||
- WinRM is enabled inside the VM (run as Administrator in VM):
|
||||
winrm quickconfig -q
|
||||
Enable-PSRemoting -Force -SkipNetworkProfileCheck
|
||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
|
||||
- Windows Firewall allows port 5985 inbound
|
||||
- WinRM HTTPS is enabled inside the VM — Deploy-WinBuild2025.ps1 post-install.ps1
|
||||
must have run successfully (creates self-signed cert + HTTPS listener on 5986)
|
||||
- Windows Firewall allows port 5986 inbound (or firewall is disabled on the guest)
|
||||
- The IP is correct (check ipconfig inside the VM)
|
||||
|
||||
Error: $_
|
||||
@@ -324,11 +314,13 @@ Error: $_
|
||||
# ── Open session ──────────────────────────────────────────────────────────────
|
||||
Write-Host "[Prepare] Opening PSSession..."
|
||||
$session = New-PSSession `
|
||||
-ComputerName $VMIPAddress `
|
||||
-Credential $credential `
|
||||
-SessionOption $sessionOptions `
|
||||
-ComputerName $VMIPAddress `
|
||||
-Credential $credential `
|
||||
-SessionOption $sessionOptions `
|
||||
-UseSSL `
|
||||
-Port 5986 `
|
||||
-Authentication Basic `
|
||||
-ErrorAction Stop
|
||||
-ErrorAction Stop
|
||||
|
||||
try {
|
||||
# ── Copy Setup-WinBuild2025.ps1 into the VM ───────────────────────────────
|
||||
@@ -423,7 +415,9 @@ try {
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
Start-Sleep -Seconds 10
|
||||
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
|
||||
if ($r) { return $true }
|
||||
} catch { }
|
||||
@@ -481,7 +475,8 @@ try {
|
||||
}
|
||||
Write-Host "[Prepare] Guest WinRM ready, reopening session..."
|
||||
$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 ──────────────────────────────────────────
|
||||
@@ -670,12 +665,9 @@ try {
|
||||
finally {
|
||||
Remove-PSSession $session -ErrorAction SilentlyContinue
|
||||
|
||||
# Restore host WinRM client settings to their original values
|
||||
if ($null -ne $prevAllowUnencrypted) {
|
||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $prevAllowUnencrypted -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
# Restore host TrustedHosts to its original value
|
||||
if ($null -ne $prevTrustedHosts) {
|
||||
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
|
||||
Step 2 — Defender: validation only — Deploy set DisableAntiSpyware=1 GPO + RTP off.
|
||||
Validation: GPO DisableAntiSpyware=1
|
||||
Step 3 — WinRM: validation only — Deploy ran Enable-PSRemoting, Basic auth,
|
||||
AllowUnencrypted, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25.
|
||||
Validation: service Running+Automatic, AllowUnencrypted, Auth/Basic, memory
|
||||
Step 3 — WinRM: validation only — Deploy ran Enable-PSRemoting, Basic auth over HTTPS,
|
||||
AllowUnencrypted=false (HTTPS-only), MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25.
|
||||
Validation: service Running+Automatic, AllowUnencrypted=false, Auth/Basic, memory
|
||||
Step 4 — Local build user account ($BuildUsername, PasswordNeverExpires, Administrators)
|
||||
Validation: user exists, enabled, PasswordNeverExpires, admin membership
|
||||
Step 4b — UAC: validation only — Deploy set EnableLUA=0, LocalAccountTokenFilterPolicy=1.
|
||||
@@ -161,6 +161,44 @@ function Assert-Step {
|
||||
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 ───────────────
|
||||
# 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.
|
||||
@@ -203,9 +241,9 @@ Assert-Step 'Defender' 'GPO DisableAntiSpyware=1 (set by Deploy-WinBuild2025)' {
|
||||
# ── Step 3: WinRM (validation only — configured by Deploy-WinBuild2025) ───────
|
||||
Write-Step "WinRM state (validation only — configured at deploy time)"
|
||||
|
||||
# Deploy-WinBuild2025.ps1 post-install.ps1 ran Enable-PSRemoting, set Basic auth,
|
||||
# AllowUnencrypted, TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h, MaxProcesses=25,
|
||||
# and StartupType=Automatic. Validated here before the build user and toolchain steps.
|
||||
# Deploy-WinBuild2025.ps1 post-install.ps1 ran Enable-PSRemoting, set Basic auth over HTTPS,
|
||||
# AllowUnencrypted=false (HTTPS/5986 only), TrustedHosts=*, MaxMemory=2048MB, IdleTimeout=2h,
|
||||
# MaxProcesses=25, StartupType=Automatic. Validated here before the build user and toolchain steps.
|
||||
|
||||
Assert-Step 'WinRM' 'service Running' {
|
||||
(Get-Service WinRM -ErrorAction Stop).Status -eq 'Running'
|
||||
@@ -213,8 +251,8 @@ Assert-Step 'WinRM' 'service Running' {
|
||||
Assert-Step 'WinRM' 'service StartType Automatic' {
|
||||
(Get-Service WinRM -ErrorAction Stop).StartType -eq 'Automatic'
|
||||
}
|
||||
Assert-Step 'WinRM' 'AllowUnencrypted=true' {
|
||||
(Get-Item WSMan:\localhost\Service\AllowUnencrypted).Value -eq 'true'
|
||||
Assert-Step 'WinRM' 'AllowUnencrypted=false (HTTPS-only)' {
|
||||
(Get-Item WSMan:\localhost\Service\AllowUnencrypted).Value -eq 'false'
|
||||
}
|
||||
Assert-Step 'WinRM' 'Auth/Basic=true' {
|
||||
(Get-Item WSMan:\localhost\Service\Auth\Basic).Value -eq 'true'
|
||||
@@ -589,6 +627,8 @@ else {
|
||||
$dotnetInstallScript = "$env:TEMP\dotnet-install.ps1"
|
||||
Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' `
|
||||
-OutFile $dotnetInstallScript -UseBasicParsing
|
||||
Assert-Hash -FilePath $dotnetInstallScript -Label 'dotnet-install.ps1' `
|
||||
-Expected $script:Hashes['DotNetInstallScript']
|
||||
|
||||
Write-Host "Installing .NET SDK $DotNetSdkVersion (channel)..."
|
||||
& $dotnetInstallScript `
|
||||
@@ -635,6 +675,8 @@ else {
|
||||
$pyInstallerPath = 'C:\CI\python_installer.exe'
|
||||
Write-Host "Downloading Python $PythonVersion installer..."
|
||||
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)..."
|
||||
$pyProc = Start-Process -FilePath $pyInstallerPath -ArgumentList @(
|
||||
@@ -689,6 +731,8 @@ else {
|
||||
Write-Host "Downloading VS Build Tools installer..."
|
||||
Remove-Item $vsInstallerPath -ErrorAction SilentlyContinue # remove any stale/corrupt copy
|
||||
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).
|
||||
# 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
|
||||
|
||||
$prevAllowUnenc = (Get-Item WSMan:\localhost\Client\AllowUnencrypted).Value
|
||||
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||
|
||||
try {
|
||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force
|
||||
$cur = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||
if ($cur -ne '*' -and $cur -notmatch [regex]::Escape($VMIPAddress)) {
|
||||
$newHosts = if ($cur) { "$cur,$VMIPAddress" } else { $VMIPAddress }
|
||||
@@ -56,7 +54,7 @@ try {
|
||||
Write-Host "`nValidating Deploy state on $VMIPAddress..." -ForegroundColor Cyan
|
||||
|
||||
$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()
|
||||
|
||||
@@ -82,8 +80,8 @@ try {
|
||||
# ── WinRM ─────────────────────────────────────────────────────────────
|
||||
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 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'
|
||||
@@ -182,6 +180,5 @@ try {
|
||||
}
|
||||
|
||||
} 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
|
||||
|
||||
$prevAllowUnenc = (Get-Item WSMan:\localhost\Client\AllowUnencrypted).Value
|
||||
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||
|
||||
try {
|
||||
Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force
|
||||
$cur = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
|
||||
if ($cur -ne '*' -and $cur -notmatch [regex]::Escape($VMIPAddress)) {
|
||||
$newHosts = if ($cur) { "$cur,$VMIPAddress" } else { $VMIPAddress }
|
||||
@@ -77,7 +75,7 @@ try {
|
||||
Write-Host "`nValidating full Setup state on $VMIPAddress..." -ForegroundColor Cyan
|
||||
|
||||
$checks = Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
||||
-Authentication Basic -SessionOption $so -ScriptBlock {
|
||||
-Authentication Basic -UseSSL -Port 5986 -SessionOption $so -ScriptBlock {
|
||||
|
||||
param($BuildUsername, $DotNetChannel, $PythonVersion)
|
||||
|
||||
@@ -109,8 +107,8 @@ try {
|
||||
# WinRM
|
||||
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 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'
|
||||
@@ -270,7 +268,7 @@ try {
|
||||
Write-Host "Applying remediation for $($failedNames.Count) failed check(s)..." -ForegroundColor Yellow
|
||||
|
||||
Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
||||
-Authentication Basic -SessionOption $so -ScriptBlock {
|
||||
-Authentication Basic -UseSSL -Port 5986 -SessionOption $so -ScriptBlock {
|
||||
|
||||
param([string[]] $FailedNames, [string] $AdminPassword)
|
||||
|
||||
@@ -296,7 +294,7 @@ try {
|
||||
# ── Re-run checks ─────────────────────────────────────────────────────────
|
||||
Write-Host "`nRe-validating after remediation..." -ForegroundColor Cyan
|
||||
$checks = Invoke-Command -ComputerName $VMIPAddress -Credential $cred `
|
||||
-Authentication Basic -SessionOption $so -ScriptBlock {
|
||||
-Authentication Basic -UseSSL -Port 5986 -SessionOption $so -ScriptBlock {
|
||||
|
||||
param($BuildUsername, $DotNetChannel, $PythonVersion)
|
||||
$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 '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 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 MaxMemoryPerShellMB >= 2048' { [int](Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB -EA Stop).Value -ge 2048 }
|
||||
$polSys = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
|
||||
@@ -369,6 +367,5 @@ try {
|
||||
}
|
||||
|
||||
} 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