Files
SecureBootCA2023/Set-SecureBootCertificateUpdate.ps1
T
Petr Stepan 9a230866a2 Initial commit: detekční skript, remediační skript a README
- Invoke-SecureBootAudit.ps1: audit Secure Boot certifikátů (Fáze 1)
  - detekce prostředí (Physical/Hyper-V VM/VMware VM)
  - parsování EFI_SIGNATURE_LIST, detekce 2011/2023 certifikátů
  - registry stav, Event Log analýza, kategorizace, JSON výstup

- Set-SecureBootCertificateUpdate.ps1: remediace dle KB5068202
  - AvailableUpdates = 0x5944
  - scheduled task s wait smyčkou (timeout 120s)
  - WhatIf, Force, VerifyOnly, WinRM

- README.md: popis problému, detekce stavů, remediační postup
2026-06-05 14:05:27 +00:00

520 lines
21 KiB
PowerShell

#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-<datum>.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 <server> -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