Files
claudetools/clients/cascades-tucson/docs/migration/scripts/entra-connect-preflight-remediation.ps1
Howard Enos 6bd416657c sync: auto-sync from HOWARD-HOME at 2026-04-22 17:39:56
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-04-22 17:39:56
2026-04-22 17:39:57 -07:00

213 lines
9.5 KiB
PowerShell

# ============================================================================
# Entra Connect Pre-flight Remediation - CS-SERVER
# ----------------------------------------------------------------------------
# Applies the three items from the 2026-04-22 readiness check:
# 1. Time sync (w32tm) - immediate, no reboot
# 2. TLS 1.2 enforcement (.NET + Schannel) - requires reboot
# 3. Install Windows Server Backup feature - no reboot
#
# Then schedules a reboot at 18:00 local time (Arizona) for Schannel changes
# to take effect. Snapshot of all pre-change state is written to
# D:\Backups\pre-entra-connect-<timestamp>\ for rollback.
#
# To cancel the scheduled reboot at any time before 18:00: shutdown /a
# ----------------------------------------------------------------------------
# Prepared: 2026-04-22
# Source: docs/migration/scripts/entra-connect-preflight-remediation.ps1
# ============================================================================
$ErrorActionPreference = 'Continue'
$TargetRebootHour = 18 # 6 PM local (Arizona)
function Section($n) {
Write-Output ''
Write-Output ('=' * 72)
Write-Output "== $n"
Write-Output ('=' * 72)
}
function Status($ok, $msg) {
$tag = if ($ok) { '[OK] ' } else { '[FAIL]' }
Write-Output "$tag $msg"
}
# ----------------------------------------------------------------------------
Section '0. Pre-flight snapshot (rollback material)'
# ----------------------------------------------------------------------------
$ts = Get-Date -Format 'yyyy-MM-dd-HHmm'
$backupDir = "D:\Backups\pre-entra-connect-$ts"
try {
New-Item -Path $backupDir -ItemType Directory -Force | Out-Null
Status $true "Backup dir: $backupDir"
} catch {
Status $false "Could not create $backupDir - $_"
exit 1
}
# Export registry keys we're about to modify
reg export "HKLM\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" "$backupDir\dotnet-64.reg" /y 2>&1 | Out-Null
reg export "HKLM\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319" "$backupDir\dotnet-32.reg" /y 2>&1 | Out-Null
reg export "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" "$backupDir\schannel-protocols-pre.reg" /y 2>&1 | Out-Null
Status $true 'Registry pre-state exported (.NET + Schannel Protocols)'
# Snapshot w32tm state
w32tm /query /configuration 2>&1 | Out-File "$backupDir\w32tm-config-pre.txt"
w32tm /query /status 2>&1 | Out-File "$backupDir\w32tm-status-pre.txt"
w32tm /query /peers 2>&1 | Out-File "$backupDir\w32tm-peers-pre.txt"
Status $true 'w32tm pre-state captured'
# Snapshot recent events (for post-reboot comparison)
try {
Get-WinEvent -LogName System -MaxEvents 200 -ErrorAction Stop |
Select-Object TimeCreated, Id, LevelDisplayName, ProviderName, Message |
Export-Csv "$backupDir\events-system-pre.csv" -NoTypeInformation
Status $true "Event log snapshot saved (200 System events)"
} catch {
Status $false "Event log snapshot: $_"
}
Write-Output ''
Write-Output ("To roll back TLS changes after reboot: " +
"`nreg import $backupDir\dotnet-64.reg `nreg import $backupDir\dotnet-32.reg " +
"`nreg import $backupDir\schannel-protocols-pre.reg `nRestart-Computer")
# ----------------------------------------------------------------------------
Section '1. Time sync fix (w32tm) - no reboot'
# ----------------------------------------------------------------------------
try {
# PDC emulator should be an authoritative time source + sync from external pool
$ntpPeers = 'time.windows.com,0x8 pool.ntp.org,0x8 time.nist.gov,0x8'
& w32tm /config /manualpeerlist:$ntpPeers /syncfromflags:manual /reliable:yes /update | Out-Null
Status $true "Configured w32tm peer list: $ntpPeers"
Restart-Service w32time -Force
Status $true 'Restarted w32time service'
Start-Sleep 4
& w32tm /resync /force 2>&1 | Out-Null
Start-Sleep 3
$statusOut = & w32tm /query /status 2>&1
$statusOut | Out-File "$backupDir\w32tm-status-post.txt"
$statusOut | Select-Object -First 20 | ForEach-Object { Write-Output " $_" }
$refIdLine = $statusOut | Where-Object { $_ -match 'ReferenceId' } | Select-Object -First 1
if ($refIdLine -match 'LOCL') {
Status $false "ReferenceId still LOCL - w32tm has not synced yet. Check firewall for outbound UDP 123."
} else {
Status $true "ReferenceId: $refIdLine (no longer LOCL)"
}
} catch {
Status $false "Time sync fix: $_"
}
# ----------------------------------------------------------------------------
Section '2. TLS 1.2 enforcement (.NET + Schannel) - requires reboot'
# ----------------------------------------------------------------------------
# .NET framework: enable strong crypto + system default TLS versions for both
# 64-bit and 32-bit runtimes. These take effect immediately for new processes.
foreach ($p in 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319',
'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319') {
try {
if (-not (Test-Path $p)) { New-Item -Path $p -Force | Out-Null }
New-ItemProperty -Path $p -Name 'SchUseStrongCrypto' -PropertyType DWord -Value 1 -Force | Out-Null
New-ItemProperty -Path $p -Name 'SystemDefaultTlsVersions' -PropertyType DWord -Value 1 -Force | Out-Null
Status $true ".NET TLS keys set at $p"
} catch {
Status $false ".NET TLS at $p - $_"
}
}
# Schannel: disable TLS 1.0 and 1.1 (both Client and Server). Enable TLS 1.2
# explicitly. Takes effect only after reboot.
$base = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols'
foreach ($proto in 'TLS 1.0','TLS 1.1') {
foreach ($role in 'Client','Server') {
$k = "$base\$proto\$role"
try {
if (-not (Test-Path $k)) { New-Item -Path $k -Force | Out-Null }
New-ItemProperty -Path $k -Name 'Enabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $k -Name 'DisabledByDefault' -PropertyType DWord -Value 1 -Force | Out-Null
Status $true "Disabled $proto $role"
} catch {
Status $false "Disable $proto $role - $_"
}
}
}
foreach ($role in 'Client','Server') {
$k = "$base\TLS 1.2\$role"
try {
if (-not (Test-Path $k)) { New-Item -Path $k -Force | Out-Null }
New-ItemProperty -Path $k -Name 'Enabled' -PropertyType DWord -Value 1 -Force | Out-Null
New-ItemProperty -Path $k -Name 'DisabledByDefault' -PropertyType DWord -Value 0 -Force | Out-Null
Status $true "Enabled TLS 1.2 $role"
} catch {
Status $false "Enable TLS 1.2 $role - $_"
}
}
# Snapshot post-state for audit
reg export "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" "$backupDir\schannel-protocols-post.reg" /y 2>&1 | Out-Null
# ----------------------------------------------------------------------------
Section '3. Install Windows Server Backup feature - no reboot'
# ----------------------------------------------------------------------------
try {
$feature = Get-WindowsFeature -Name Windows-Server-Backup
if ($feature.InstallState -eq 'Installed') {
Status $true 'Windows-Server-Backup already installed'
} else {
$result = Install-WindowsFeature -Name Windows-Server-Backup -IncludeManagementTools
if ($result.Success) {
Status $true "Installed Windows-Server-Backup. RestartNeeded: $($result.RestartNeeded)"
} else {
Status $false "Install-WindowsFeature returned Success=False"
}
}
} catch {
Status $false "WSB install: $_"
}
# ----------------------------------------------------------------------------
Section "4. Schedule reboot for $TargetRebootHour`:00 local"
# ----------------------------------------------------------------------------
$now = Get-Date
$target = $now.Date.AddHours($TargetRebootHour)
if ($target -lt $now) { $target = $target.AddDays(1) }
$seconds = [int]($target - $now).TotalSeconds
Write-Output "Current time: $now"
Write-Output "Target reboot: $target"
Write-Output "Delay: $seconds seconds ($([math]::Round($seconds/60,1)) minutes)"
if ($seconds -lt 60) {
Status $false "Target reboot is less than 1 minute away - not scheduling. Run again closer to window."
} elseif ($seconds -gt 86400) {
Status $false "Target reboot more than 24 hours away - $seconds seconds. Rejecting."
} else {
# /r restart, /t timeout, /d p:4:2 = Planned / Application: Maintenance
# /c comment (<512 chars) shows in user popup + event log
# No /f - we want graceful shutdown of services (AD, Hyper-V, QB VSS)
$comment = 'Entra Connect preflight: TLS 1.2 + time sync enforcement. Cancel: shutdown /a'
shutdown /r /t $seconds /d p:4:2 /c $comment
if ($LASTEXITCODE -eq 0) {
Status $true "Reboot scheduled for $target (in $seconds sec). Cancel with: shutdown /a"
} else {
Status $false "shutdown command returned exit $LASTEXITCODE"
}
}
# ----------------------------------------------------------------------------
Section '5. Summary'
# ----------------------------------------------------------------------------
Write-Output "Backup/rollback dir: $backupDir"
Write-Output "Reboot scheduled: $target"
Write-Output 'Cancel reboot: shutdown /a'
Write-Output ''
Write-Output 'Post-reboot verification:'
Write-Output ' 1. w32tm /query /status - confirm ReferenceId is no longer LOCL'
Write-Output ' 2. nltest /dsgetdc:cascades.local - confirm DC is responding'
Write-Output ' 3. Re-run entra-connect-readiness.ps1 for full pass/fail sweep'
Write-Output ''
Write-Output "Completed at $(Get-Date)"