param( [string]$EvidenceDir = "C:\Wooo\Agent99\evidence" ) $ErrorActionPreference = "Stop" New-Item -ItemType Directory -Force -Path $EvidenceDir | Out-Null $stamp = Get-Date -Format "yyyyMMdd-HHmmss" $evidencePath = Join-Path $EvidenceDir "agent99-windows-update-policy-$stamp.json" $logPath = Join-Path $EvidenceDir "agent99-windows-update-policy-$stamp.log" function Write-PolicyLog { param([string]$Message) "$(Get-Date -Format o) $Message" | Tee-Object -FilePath $logPath -Append | Out-Null } function Get-RegistrySnapshot { param([string]$Path) if (-not (Test-Path $Path)) { return [pscustomobject]@{ path = $Path; exists = $false; values = @{} } } $item = Get-ItemProperty -Path $Path $values = @{} foreach ($prop in $item.PSObject.Properties) { if ($prop.Name -notmatch "^PS") { $values[$prop.Name] = $prop.Value } } [pscustomobject]@{ path = $Path; exists = $true; values = $values } } $windowsUpdatePolicy = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" $auPolicy = Join-Path $windowsUpdatePolicy "AU" $uxSettings = "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" $before = @( Get-RegistrySnapshot $windowsUpdatePolicy Get-RegistrySnapshot $auPolicy Get-RegistrySnapshot $uxSettings ) Write-PolicyLog "apply_start windows_update_no_surprise_reboot" New-Item -Path $windowsUpdatePolicy -Force | Out-Null New-Item -Path $auPolicy -Force | Out-Null New-Item -Path $uxSettings -Force | Out-Null # Keep Windows Update enabled, but prevent unattended surprise restarts. New-ItemProperty -Path $auPolicy -Name "NoAutoRebootWithLoggedOnUsers" -Value 1 -PropertyType DWord -Force | Out-Null New-ItemProperty -Path $auPolicy -Name "AlwaysAutoRebootAtScheduledTime" -Value 0 -PropertyType DWord -Force | Out-Null New-ItemProperty -Path $auPolicy -Name "AUOptions" -Value 3 -PropertyType DWord -Force | Out-Null New-ItemProperty -Path $auPolicy -Name "ScheduledInstallDay" -Value 0 -PropertyType DWord -Force | Out-Null New-ItemProperty -Path $auPolicy -Name "ScheduledInstallTime" -Value 3 -PropertyType DWord -Force | Out-Null # Make the interactive active-hours UI reflect an ops-safe window. New-ItemProperty -Path $uxSettings -Name "ActiveHoursStart" -Value 0 -PropertyType DWord -Force | Out-Null New-ItemProperty -Path $uxSettings -Name "ActiveHoursEnd" -Value 23 -PropertyType DWord -Force | Out-Null New-ItemProperty -Path $uxSettings -Name "SmartActiveHoursState" -Value 0 -PropertyType DWord -Force | Out-Null $after = @( Get-RegistrySnapshot $windowsUpdatePolicy Get-RegistrySnapshot $auPolicy Get-RegistrySnapshot $uxSettings ) $pendingRebootKeys = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired", "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" ) $pending = @() foreach ($key in $pendingRebootKeys) { if ($key -like "*Session Manager") { $value = (Get-ItemProperty -Path $key -Name "PendingFileRenameOperations" -ErrorAction SilentlyContinue).PendingFileRenameOperations $pending += [pscustomobject]@{ path = $key; signal = "PendingFileRenameOperations"; present = [bool]$value } } else { $pending += [pscustomobject]@{ path = $key; signal = "key_exists"; present = [bool](Test-Path $key) } } } try { gpupdate.exe /target:computer /force | Out-File -FilePath $logPath -Append -Encoding UTF8 $gpupdateOk = $true } catch { $gpupdateOk = $false Write-PolicyLog "gpupdate_failed error=$($_.Exception.Message)" } $result = [pscustomobject]@{ timestamp = (Get-Date -Format o) applied = $true policy = "windows_update_no_surprise_reboot" gpupdateOk = $gpupdateOk before = $before after = $after pendingRebootSignals = $pending evidenceLog = $logPath } $result | ConvertTo-Json -Depth 8 | Set-Content -Encoding UTF8 $evidencePath Write-PolicyLog "apply_complete evidence=$evidencePath" Write-Host $evidencePath