#Requires -Version 5.1 <# .SYNOPSIS Remediační skript pro aktualizaci Secure Boot certifikátů (KB5068202). .DESCRIPTION Nastaví registry klíče pro zahájení aktualizace Secure Boot certifikátů (přechod z 2011 na 2023 sadu) a spustí scheduled task. Metoda: Registry key (KB5068202) Alternativy: GPO (KB5068198), WinCS CLI (KB5068197) Postup remediace: 1. Skript nastaví AvailableUpdates = 0x5944 v registry 2. Scheduled task \Microsoft\Windows\PI\Secure-Boot-Update se spustí (nebo čeká na příští plánovaný běh každých 12 hodin) 3. Certifikáty se aplikují při příštím restartu 4. Výsledek ověřit přes EventID 1808 (úspěch) nebo 1801/1795 (chyba) DŮLEŽITÉ: - Aktualizace vyžaduje RESTART pro aplikaci certifikátů v Boot Manageru - Neúspěšná aktualizace NEOHROZÍ boot serveru — server běží dál - Po aplikaci čekat min. 48 hodin a jeden nebo více restartů - Na Hyper-V VM s Event 1795: ověřit verzi hostitele (fix dostupný od 3/2026) .PARAMETER ServerName Název nebo pole názvů serverů pro vzdálené spuštění přes WinRM. Pokud není zadán, skript se spustí lokálně. .PARAMETER Credential Přihlašovací údaje pro vzdálené připojení. .PARAMETER Force Aplikuje registry klíče i na serverech, které již mají 2023 certifikáty nebo kde již aktualizace proběhla (UEFICA2023Status = 2). .PARAMETER SkipScheduledTask Nespustí scheduled task po nastavení registry. Task se spustí sám při příštím plánovaném běhu (každých 12 hodin). .PARAMETER VerifyOnly Pouze zobrazí aktuální stav bez provedení změn (read-only, ekvivalent -WhatIf). .PARAMETER LogPath Cesta k logovacímu souboru. Default: .\SecureBootRemediation-.log .EXAMPLE # WhatIf — zobrazí co by se stalo, bez změn .\Set-SecureBootCertificateUpdate.ps1 -WhatIf .EXAMPLE # Lokální spuštění .\Set-SecureBootCertificateUpdate.ps1 .EXAMPLE # Vzdálené spuštění na jednom serveru .\Set-SecureBootCertificateUpdate.ps1 -ServerName SERVER01 .EXAMPLE # Vzdálené spuštění na více serverech .\Set-SecureBootCertificateUpdate.ps1 -ServerName SERVER01,SERVER02,SERVER03 .EXAMPLE # Pouze ověření bez změn .\Set-SecureBootCertificateUpdate.ps1 -ServerName SERVER01 -VerifyOnly .NOTES Reference: KB5062710, KB5068202, KB5085046, KB5085790 Vyžaduje spuštění jako Administrator (lokálně i vzdáleně). Vzdálené spuštění vyžaduje WinRM (standardně aktivní na Windows Server). AvailableUpdates = 0x5944 dle KB5068202 (KEK + UEFI CA + Windows UEFI CA + Boot Manager). #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [Parameter(Position = 0)] [string[]]$ServerName, [System.Management.Automation.PSCredential]$Credential, [switch]$Force, [switch]$SkipScheduledTask, [switch]$VerifyOnly, [string]$LogPath = ".\SecureBootRemediation-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" ) $ErrorActionPreference = 'SilentlyContinue' Set-StrictMode -Off #region ── Logging ──────────────────────────────────────────────────────────── function Write-Log { param( [string]$Message, [ValidateSet('INFO','WARN','ERROR','SUCCESS','ACTION')] [string]$Level = 'INFO' ) $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $line = "[$ts] [$Level] $Message" $color = switch ($Level) { 'SUCCESS' { 'Green' } 'WARN' { 'Yellow' } 'ERROR' { 'Red' } 'ACTION' { 'Cyan' } default { 'Gray' } } Write-Host $line -ForegroundColor $color try { $line | Out-File -FilePath $LogPath -Append -Encoding UTF8 -Force } catch { } } #endregion #region ── Core remediation logic (spouští se lokálně nebo přes Invoke-Command) ── $RemediationScriptBlock = { param( [bool]$WhatIfMode, [bool]$ForceMode, [bool]$VerifyOnlyMode, [bool]$SkipTask ) $result = [ordered]@{ Hostname = $env:COMPUTERNAME Timestamp = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ssZ') PreState = $null ActionsApplied = @() PostState = $null Status = 'Unknown' Message = '' RequiresRestart = $false } # Registry konstanty (KB5068202) $REG_SECUREBOOT = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot' $REG_SERVICING = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing' $AVAILABLE_UPDATES_VALUE = 0x5944 # KEK + UEFI CA + Windows UEFI CA + Boot Manager (KB5068202) $TASK_PATH = '\Microsoft\Windows\PI\Secure-Boot-Update' #-- Funkce: zjistit aktuální stav ----------------------------------------- function Get-State { $s = [ordered]@{ FirmwareType = 'Unknown' SecureBootEnabled = $false SecureBootSupported = $false AvailableUpdates = $null HighConfidenceOptOut = $null UEFICA2023Status = $null UEFICA2023StatusText = 'Unknown' UEFICA2023Error = $null WindowsUEFICA2023Capable = $null LastRelevantEvent = $null LastRelevantEventTime = $null } # Firmware type $fw = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue if ($fw) { $s.FirmwareType = if ($fw.PEFirmwareType -eq 2) { 'UEFI' } else { 'Legacy BIOS' } } elseif (Test-Path $REG_SECUREBOOT) { $s.FirmwareType = 'UEFI' } else { $s.FirmwareType = 'Legacy BIOS' } # Secure Boot stav try { $r = Confirm-SecureBootUEFI -ErrorAction Stop $s.SecureBootSupported = $true $s.SecureBootEnabled = [bool]$r } catch { } # Registry hodnoty $mainProps = Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue if ($mainProps) { $s.AvailableUpdates = $mainProps.AvailableUpdates $s.HighConfidenceOptOut = $mainProps.HighConfidenceOptOut } $svcProps = Get-ItemProperty $REG_SERVICING -ErrorAction SilentlyContinue if ($svcProps) { $s.UEFICA2023Status = $svcProps.UEFICA2023Status $s.UEFICA2023Error = $svcProps.UEFICA2023Error $s.WindowsUEFICA2023Capable = $svcProps.WindowsUEFICA2023Capable $s.UEFICA2023StatusText = switch ($s.UEFICA2023Status) { 0 { 'NotStarted' } 1 { 'InProgress' } 2 { 'Success' } 3 { 'Failed' } $null { 'KeyNotPresent' } default { "Unknown ($($s.UEFICA2023Status))" } } } # Poslední relevantní event try { $evt = Get-WinEvent -FilterHashtable @{ LogName = 'System' Id = @(1795,1801,1808) } -MaxEvents 1 -ErrorAction Stop | Select-Object -First 1 if ($evt) { $s.LastRelevantEvent = $evt.Id $s.LastRelevantEventTime = $evt.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') } } catch { } return $s } #-- Představ aktuální stav ------------------------------------------------ $result.PreState = Get-State $pre = $result.PreState # Abort podmínky (bez -Force) if (-not $VerifyOnlyMode -and -not $WhatIfMode) { if ($pre.FirmwareType -ne 'UEFI') { $result.Status = 'Skipped' $result.Message = 'Legacy BIOS — Secure Boot není podporováno, remediace není možná.' return $result } if (-not $pre.SecureBootSupported) { $result.Status = 'Skipped' $result.Message = 'Secure Boot není podporováno nebo povoleno — remediace přeskočena.' return $result } if ($pre.UEFICA2023Status -eq 2 -and -not $ForceMode) { $result.Status = 'Skipped' $result.Message = "Aktualizace již byla úspěšně dokončena (UEFICA2023Status=2). Použijte -Force pro opakované spuštění." return $result } if ($pre.WindowsUEFICA2023Capable -eq 0 -and -not $ForceMode) { $result.Status = 'Warning' $result.Message = "WindowsUEFICA2023Capable=0 — firmware nemusí podporovat nové certifikáty. Ověřte firmware u výrobce. Použijte -Force pro vynucení." return $result } if ($pre.LastRelevantEvent -eq 1795) { $result.Status = 'Warning' $result.Message = "Detekován Event 1795 (Hyper-V known issue). Ověřte verzi hostitele — fix je dostupný od 3/2026 (KB5085790). Použijte -Force pro pokračování." if (-not $ForceMode) { return $result } } } if ($VerifyOnlyMode) { $result.Status = 'VerifyOnly' $result.Message = 'VerifyOnly mode — žádné změny neprovedeny.' return $result } if ($WhatIfMode) { $result.ActionsApplied += "WhatIf: Nastavil by HKLM:\...\SecureBoot\AvailableUpdates = 0x$($AVAILABLE_UPDATES_VALUE.ToString('X4'))" $result.ActionsApplied += "WhatIf: Zajistil by HighConfidenceOptOut = 0" if (-not $SkipTask) { $result.ActionsApplied += "WhatIf: Spustil by scheduled task '$TASK_PATH'" } $result.Status = 'WhatIf' $result.Message = 'WhatIf mode — žádné změny neprovedeny.' return $result } #-- Aplikovat registry klíče (KB5068202) ---------------------------------- # 1) Zajistit existenci SecureBoot klíče (obvykle existuje) if (-not (Test-Path $REG_SECUREBOOT)) { try { New-Item -Path $REG_SECUREBOOT -Force -ErrorAction Stop | Out-Null $result.ActionsApplied += "Vytvořen registry klíč: $REG_SECUREBOOT" } catch { $result.Status = 'Error' $result.Message = "Nelze vytvořit registry klíč $REG_SECUREBOOT : $($_.Exception.Message)" return $result } } # 2) Nastavit AvailableUpdates = 0x5944 try { Set-ItemProperty -Path $REG_SECUREBOOT -Name 'AvailableUpdates' ` -Value $AVAILABLE_UPDATES_VALUE -Type DWord -Force -ErrorAction Stop $result.ActionsApplied += "Nastaveno AvailableUpdates = 0x$($AVAILABLE_UPDATES_VALUE.ToString('X4')) (KB5068202)" } catch { $result.Status = 'Error' $result.Message = "Chyba při nastavení AvailableUpdates: $($_.Exception.Message)" return $result } # 3) HighConfidenceOptOut musí být 0 (nebo neexistovat) — jinak se update neprovede $optOut = (Get-ItemProperty $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -ErrorAction SilentlyContinue) if ($optOut -and $optOut.HighConfidenceOptOut -ne 0) { try { Set-ItemProperty -Path $REG_SECUREBOOT -Name 'HighConfidenceOptOut' ` -Value 0 -Type DWord -Force -ErrorAction Stop $result.ActionsApplied += "Resetován HighConfidenceOptOut = 0 (byl $($optOut.HighConfidenceOptOut))" } catch { $result.ActionsApplied += "WARN: Nepodařilo se resetovat HighConfidenceOptOut: $($_.Exception.Message)" } } # 4) Zajistit existenci Servicing podklíče if (-not (Test-Path $REG_SERVICING)) { try { New-Item -Path $REG_SERVICING -Force -ErrorAction Stop | Out-Null $result.ActionsApplied += "Vytvořen podklíč: $REG_SERVICING" } catch { } } #-- Spustit scheduled task ------------------------------------------------ if (-not $SkipTask) { try { $task = Get-ScheduledTask -TaskPath '\Microsoft\Windows\PI\' ` -TaskName 'Secure-Boot-Update' -ErrorAction Stop if ($task) { Start-ScheduledTask -TaskPath '\Microsoft\Windows\PI\' ` -TaskName 'Secure-Boot-Update' -ErrorAction Stop $result.ActionsApplied += "Spuštěn scheduled task: $TASK_PATH" # Čekat na dokončení tasku (timeout 120 sekund) $timeoutSec = 120 $intervalSec = 3 $elapsed = 0 $finalState = 'Unknown' do { Start-Sleep -Seconds $intervalSec $elapsed += $intervalSec $finalState = (Get-ScheduledTask -TaskPath '\Microsoft\Windows\PI\' ` -TaskName 'Secure-Boot-Update' ` -ErrorAction SilentlyContinue).State } while ($finalState -eq 'Running' -and $elapsed -lt $timeoutSec) if ($elapsed -ge $timeoutSec -and $finalState -eq 'Running') { $result.ActionsApplied += "WARN: Task stále běží po $timeoutSec s — pokračuji bez čekání. Ověřit stav ručně." } else { $result.ActionsApplied += "Task dokončen za ${elapsed}s — stav: $finalState" } } } catch { $result.ActionsApplied += "INFO: Scheduled task '$TASK_PATH' nebyl nalezen nebo se nepodařilo spustit: $($_.Exception.Message)" $result.ActionsApplied += "INFO: Task se spustí automaticky při příštím plánovaném běhu (každých 12 hodin)." } } else { $result.ActionsApplied += "INFO: Spuštění scheduled task přeskočeno (-SkipScheduledTask). Task se spustí sám (každých 12 hodin)." } #-- Post-stav (okamžitě po aplikaci, před restartem) --------------------- Start-Sleep -Seconds 2 $result.PostState = Get-State $result.RequiresRestart = $true $result.Status = 'Applied' $result.Message = "Registry klíče nastaveny. Server vyžaduje RESTART pro aplikaci certifikátů. Po restartu ověřit EventID 1808 v System logu." return $result } #endregion #region ── Výstup výsledku ──────────────────────────────────────────────────── function Write-ResultSummary { param($res) $line = '-' * 60 Write-Log $line Write-Log "SERVER: $($res.Hostname) | $($res.Timestamp)" $_statusLevel = switch ($res.Status) { 'Applied' { 'SUCCESS' } 'Error' { 'ERROR' } 'Warning' { 'WARN' } 'Skipped' { 'WARN' } default { 'INFO' } } Write-Log "STATUS: $($res.Status) — $($res.Message)" -Level $_statusLevel $pre = $res.PreState if ($pre) { Write-Log " Pre-state:" Write-Log " Firmware : $($pre.FirmwareType)" Write-Log " Secure Boot : podporováno=$($pre.SecureBootSupported), povoleno=$($pre.SecureBootEnabled)" Write-Log " AvailableUpdates : $($pre.AvailableUpdates)" Write-Log " UEFICA2023Status : $($pre.UEFICA2023Status) ($($pre.UEFICA2023StatusText))" Write-Log " WindowsCapable : $($pre.WindowsUEFICA2023Capable)" if ($pre.UEFICA2023Error) { Write-Log " ! UEFICA2023Error : $($pre.UEFICA2023Error)" -Level 'WARN' } if ($pre.LastRelevantEvent) { Write-Log " Poslední event : EventID $($pre.LastRelevantEvent) ($($pre.LastRelevantEventTime))" } } if ($res.ActionsApplied -and $res.ActionsApplied.Count -gt 0) { Write-Log " Provedené akce:" foreach ($action in $res.ActionsApplied) { $lvl = if ($action -like 'WhatIf:*' -or $action -like 'INFO:*') { 'INFO' } elseif ($action -like 'WARN:*') { 'WARN' } else { 'ACTION' } Write-Log " $action" -Level $lvl } } $post = $res.PostState if ($post -and $res.Status -eq 'Applied') { Write-Log " Post-state (před restartem):" Write-Log " AvailableUpdates : $($post.AvailableUpdates)" Write-Log " UEFICA2023Status : $($post.UEFICA2023Status) ($($post.UEFICA2023StatusText))" } if ($res.RequiresRestart) { Write-Log " *** VYŽADOVÁN RESTART pro aplikaci certifikátů ***" -Level 'WARN' Write-Log " Po restartu ověřit: Get-WinEvent -FilterHashtable @{LogName='System';Id=1808} -MaxEvents 5" } } #endregion #region ── Main ─────────────────────────────────────────────────────────────── $isWhatIf = $WhatIfPreference -eq [Management.Automation.ActionPreference]::Continue $isVerify = $VerifyOnly.IsPresent $isForce = $Force.IsPresent $isSkipTask = $SkipScheduledTask.IsPresent Write-Log ('=' * 60) Write-Log "Set-SecureBootCertificateUpdate — START" Write-Log "Parametry: WhatIf=$isWhatIf, Force=$isForce, VerifyOnly=$isVerify, SkipTask=$isSkipTask" Write-Log "Log: $LogPath" Write-Log ('=' * 60) $results = @() if (-not $ServerName -or $ServerName.Count -eq 0) { # ── Lokální spuštění ── Write-Log "Spouštím lokálně na: $env:COMPUTERNAME" -Level 'INFO' if (-not $isWhatIf -and -not $isVerify) { if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Nastavit Secure Boot registry klíče (AvailableUpdates=0x5944)")) { Write-Log "Uživatel zamítl akci — konec." -Level 'WARN' exit 0 } } $res = & $RemediationScriptBlock -WhatIfMode $isWhatIf -ForceMode $isForce ` -VerifyOnlyMode $isVerify -SkipTask $isSkipTask Write-ResultSummary $res $results += $res } else { # ── Vzdálené spuštění ── Write-Log "Cílové servery ($($ServerName.Count)): $($ServerName -join ', ')" -Level 'INFO' $invokeParams = @{ ComputerName = $ServerName ScriptBlock = $RemediationScriptBlock ArgumentList = @($isWhatIf, $isForce, $isVerify, $isSkipTask) ErrorAction = 'SilentlyContinue' ThrottleLimit = 10 } if ($Credential) { $invokeParams['Credential'] = $Credential } $remoteResults = Invoke-Command @invokeParams foreach ($res in $remoteResults) { Write-ResultSummary $res $results += $res } # Servery, které neodpověděly $respondedHosts = $remoteResults | ForEach-Object { $_.Hostname } foreach ($srv in $ServerName) { if ($srv -notin $respondedHosts) { Write-Log " ! Server '$srv' neodpověděl (WinRM nedostupný nebo přihlášení selhalo)" -Level 'ERROR' $results += [ordered]@{ Hostname = $srv; Status = 'Unreachable'; Message = 'WinRM nedostupný' PreState = $null; PostState = $null; ActionsApplied = @(); RequiresRestart = $false } } } } #-- Souhrn ------------------------------------------------------------------ Write-Log ('=' * 60) Write-Log "SOUHRN REMEDIACE" $grouped = $results | Group-Object Status foreach ($g in $grouped) { $lvl = switch ($g.Name) { 'Applied' { 'SUCCESS' } 'Error' { 'ERROR' } 'Unreachable' { 'ERROR' } 'Skipped' { 'WARN' } 'Warning' { 'WARN' } default { 'INFO' } } Write-Log " $($g.Name): $($g.Count) server(ů) [$(($g.Group.Hostname) -join ', ')]" -Level $lvl } $needRestart = @($results | Where-Object { $_.RequiresRestart }) if ($needRestart.Count -gt 0) { Write-Log '' Write-Log "VYŽADOVÁN RESTART ($($needRestart.Count) server(ů)): $($needRestart.Hostname -join ', ')" -Level 'WARN' Write-Log "Po restartu ověřit EventID 1808 v System logu nebo spustit:" Write-Log " Invoke-Command -ComputerName -ScriptBlock { Get-WinEvent -FilterHashtable @{LogName='System';Id=@(1801,1808)} -MaxEvents 5 | Select TimeCreated,Id,Message }" -Level 'INFO' } Write-Log ('=' * 60) Write-Log "Set-SecureBootCertificateUpdate — KONEC | Log: $LogPath" Write-Log ('=' * 60) # Vrátit výsledky jako objekty pro pipeline $results #endregion