213 lines
9.5 KiB
PowerShell
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)"
|