Files
SecureBootCA2023/Invoke-SecureBootRemediation.ps1
Petr Stepan 2712030aea Clean repo
2026-06-08 15:38:04 +02:00

1611 lines
75 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#Requires -Version 5.1
<#
.SYNOPSIS
Detekce a interaktivní remediace Secure Boot certifikátů (KB5062710 / KB5068202).
.DESCRIPTION
Script zkontroluje přítomnost nových 2023 Secure Boot certifikátů, zobrazí stav
ve čtyřech sekcích a nabídne remediaci.
Pokud jsou povinné 2023 certifikáty (Windows UEFI CA 2023 v DB +
Microsoft Corporation KEK 2K CA 2023 v KEK) PŘED spuštěním remediace přítomny,
script skončí s informací, že není nutná žádná akce.
Výjimka: parametr -Force umožní remediaci přesto spustit.
Kritéria úspěšné remediace (post-remediation — všechny 4 podmínky):
1. Windows UEFI CA 2023 v DB + Microsoft Corporation KEK 2K CA 2023 v KEK
2. AvailableUpdates = 0x0 nebo 0x4000
3. UEFICA2023Status = "Updated"
4. UEFICA2023Error = 0 nebo neexistuje
Průchod AvailableUpdates: 0x5944 -> 0x5904 -> 0x5104 -> 0x4104 -> 0x4100 -> 0x4000 -> 0x0
Hodnota 0x5944 se nastavuje pouze jednou (při prvním spuštění nebo po dokončení);
systém poté bity sám odečítá.
.PARAMETER CheckOnly
Pouze zobrazí stav bez dotazů a změn.
Exit kódy: 0 = hotovo, 1 = nutná akce, 2 = blokováno.
.PARAMETER AssumeYes
Přeskočí interaktivní dotaz a remediaci rovnou aplikuje (pokud je smysluplná).
.PARAMETER Force
Spustí remediaci i v případě, že povinné certifikáty jsou již přítomny.
.PARAMETER AutoRestart
Po staged Boot Manageru (AU=0x4100) restartuje server automaticky bez dotazu.
POZOR: server bude restartován okamžitě bez potvrzení.
.NOTES
Reference: KB5062710, KB5068202, KB5066835 (min. build 10/2025), KB5085790.
Vyžaduje Administrator pro zápis registry a spuštění servicing task.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param(
[switch]$CheckOnly,
[switch]$AssumeYes,
[switch]$Force,
[switch]$AutoRestart,
# Písmeno pro dočasný mount ESP (default S:). Změňte pokud S: je obsazeno.
[ValidatePattern('^[A-Za-z]$')]
[string]$EspDriveLetter = 'S'
)
$ErrorActionPreference = 'SilentlyContinue'
Set-StrictMode -Off
# Vynutit UTF-8 výstup (diakritika i ve Windows PowerShell 5.1)
try {
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[Console]::OutputEncoding = $utf8NoBom
$OutputEncoding = $utf8NoBom
} catch { }
#region ── Konstanty ──────────────────────────────────────────────────────────
$REG_SECUREBOOT = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot'
$REG_SERVICING = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing'
$AVAILABLE_UPDATES_VALUE = 0x5944
$TASK_PATH = '\Microsoft\Windows\PI\'
$TASK_NAME = 'Secure-Boot-Update'
$TASK_TIMEOUT_SEC = 180
$WORK_ROOT = Join-Path $env:ProgramData 'SecureBootCA2023'
$STATE_FILE = Join-Path $WORK_ROOT 'state.json'
$CN_KEK2023 = 'KEK 2K CA 2023'
$CN_WINUEFI2023 = 'Windows UEFI CA 2023'
$CN_UEFI2023 = 'Microsoft UEFI CA 2023'
$CN_OPTROM2023 = 'Option ROM UEFI CA 2023'
$CN_PCA2011 = 'Windows Production PCA 2011'
$SYM_DONE = '+'
$SYM_FAIL = '!'
$SYM_PENDING = ' '
# Popisy EventID pro sekci UDÁLOSTI
$EVENT_DESC = @{
1795 = 'Chyba firmwaru — aktualizace selhala'
1796 = 'Selhání zápisu UEFI proměnné'
1799 = 'Informační: aktualizace dokončena'
1800 = 'Aktualizace zahájena'
1801 = 'Čeká na podmínky (monitor)'
1802 = 'Pozastaveno — problém firmwaru'
1803 = 'Odloženo (monitorovací podmínka)'
1808 = 'Certifikáty úspěšně aplikovány'
}
#endregion
#region ── Log / barevný výstup ───────────────────────────────────────────────
$script:LogFile = Join-Path $WORK_ROOT 'SecureBootRemediation.log'
$script:LogBuffer = New-Object System.Collections.Generic.List[string]
function Add-LogLine {
param([string]$Text)
[void]$script:LogBuffer.Add(('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Text))
}
function Flush-Log {
if ($script:LogBuffer.Count -eq 0) { return }
try {
if (-not (Test-Path $WORK_ROOT)) { New-Item -Path $WORK_ROOT -ItemType Directory -Force | Out-Null }
[System.IO.File]::AppendAllText(
$script:LogFile,
(($script:LogBuffer -join "`r`n") + "`r`n"),
(New-Object System.Text.UTF8Encoding($false))
)
$script:LogBuffer.Clear()
} catch { }
}
function Write-Line {
param([string]$Text = '', [string]$Color = 'Gray', [switch]$NoLog)
Write-Host $Text -ForegroundColor $Color
if (-not $NoLog) { Add-LogLine $Text }
}
function Write-Rule {
param([string]$Color = 'DarkCyan')
Write-Host ('=' * 64) -ForegroundColor $Color
}
function Write-Head {
param([string]$Text)
Write-Host ''
Write-Host $Text -ForegroundColor Cyan
Add-LogLine "== $Text =="
}
function Write-KV {
param(
[string]$Key,
[string]$Value,
[string]$ValueColor = 'White',
[string]$Note,
[string]$NoteColor = 'DarkGray'
)
Write-Host (" {0,-28}: " -f $Key) -ForegroundColor Gray -NoNewline
Write-Host $Value -ForegroundColor $ValueColor -NoNewline
if ($Note) { Write-Host (" $Note") -ForegroundColor $NoteColor } else { Write-Host '' }
Add-LogLine ("{0}: {1} {2}" -f $Key, $Value, $Note)
}
function Write-Check {
param(
[ValidateSet('Done','Pending','Fail','Info')][string]$State,
[string]$Text,
[string]$Note,
[string]$NoteColor = 'Yellow'
)
switch ($State) {
'Done' { $mark = "[$SYM_DONE]"; $mc = 'Green'; $tc = 'White' }
'Fail' { $mark = "[$SYM_FAIL]"; $mc = 'Red'; $tc = 'Red' }
'Pending' { $mark = "[ ]"; $mc = 'DarkGray'; $tc = 'Gray' }
'Info' { $mark = "[ ]"; $mc = 'DarkGray'; $tc = 'DarkGray' }
}
Write-Host (" {0} " -f $mark) -ForegroundColor $mc -NoNewline
Write-Host $Text -ForegroundColor $tc -NoNewline
if ($Note) { Write-Host (" $Note") -ForegroundColor $NoteColor } else { Write-Host '' }
Add-LogLine (" [{0}] {1} {2}" -f $State, $Text, $Note)
}
#endregion
#region ── Detekce — prostředí / HW / Secure Boot ────────────────────────────
function Get-EnvironmentType {
try {
$cs = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
$mfr = [string]$cs.Manufacturer
$mdl = [string]$cs.Model
if ($mfr -like '*VMware*') { return 'VMware VM' }
if ($mfr -like '*Microsoft*' -and $mdl -eq 'Virtual Machine') { return 'Hyper-V VM' }
if ($mdl -like '*Virtual*' -or $mfr -like '*QEMU*' -or $mfr -like '*Xen*') { return 'Other VM' }
return 'Physical'
} catch { return 'Unknown' }
}
function Get-HardwareInfo {
$hw = [ordered]@{
Manufacturer = 'Unknown'
Model = 'Unknown'
FirmwareType = 'Unknown'
BiosVersion = 'Unknown'
BiosReleaseDate = 'Unknown'
}
try {
$cs = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
$hw.Manufacturer = [string]$cs.Manufacturer
$hw.Model = [string]$cs.Model
} catch { }
try {
$bios = Get-CimInstance Win32_BIOS -ErrorAction Stop
$hw.BiosVersion = [string]$bios.SMBIOSBIOSVersion
if ($bios.ReleaseDate) {
$hw.BiosReleaseDate = ([datetime]$bios.ReleaseDate).ToString('yyyy-MM-dd')
}
} catch { }
$fw = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue
if ($fw) {
$hw.FirmwareType = if ($fw.PEFirmwareType -eq 2) { 'UEFI' } else { 'Legacy BIOS' }
} elseif (Test-Path $REG_SECUREBOOT) {
$hw.FirmwareType = 'UEFI'
} else {
$hw.FirmwareType = 'Legacy BIOS'
}
return $hw
}
function Get-SecureBootState {
$s = [ordered]@{ IsUEFI = $false; IsSupported = $false; IsEnabled = $false; Error = $null }
$fw = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue
$s.IsUEFI = ($fw -and $fw.PEFirmwareType -eq 2) -or (Test-Path $REG_SECUREBOOT)
if (-not $s.IsUEFI) { return $s }
try {
$r = Confirm-SecureBootUEFI -ErrorAction Stop
$s.IsSupported = $true
$s.IsEnabled = [bool]$r
} catch {
$m = $_.Exception.Message
if ($m -like '*not supported*' -or $m -like '*Cmdlet not supported*') {
$s.IsSupported = $false
} elseif ($m -like '*disabled*') {
$s.IsSupported = $true; $s.IsEnabled = $false
} else {
$s.IsSupported = $true; $s.IsEnabled = $false; $s.Error = $m
}
}
return $s
}
function Get-SecureBootMode {
$setup = $null; $audit = $null; $deployed = $null
try { $setup = (Get-SecureBootUEFI -Name SetupMode -ErrorAction Stop).Bytes[0] } catch { }
try { $audit = (Get-SecureBootUEFI -Name AuditMode -ErrorAction Stop).Bytes[0] } catch { }
try { $deployed = (Get-SecureBootUEFI -Name DeployedMode -ErrorAction Stop).Bytes[0] } catch { }
if ($setup -eq 1) { return 'Setup' }
if ($audit -eq 1) { return 'Audit' }
if ($deployed -eq 1) { return 'Deployed' }
if ($null -ne $setup) { return 'User' }
return 'Unknown'
}
function Get-BitLockerInfo {
$i = [ordered]@{ Status = 'Unknown'; Protectors = ''; UsesPcr = $false }
try {
$v = Get-BitLockerVolume -MountPoint $env:SystemDrive -ErrorAction Stop
if ($v) {
$i.Status = [string]$v.ProtectionStatus
$types = @($v.KeyProtector | ForEach-Object { [string]$_.KeyProtectorType })
$i.Protectors = ($types -join ', ')
$i.UsesPcr = [bool](@($types | Where-Object { $_ -like 'Tpm*' }).Count)
}
} catch { $i.Status = 'N/A' }
return $i
}
#endregion
#region ── Detekce — certifikáty / registry / události ───────────────────────
function Parse-EFISignatureList {
# Parsuje EFI_SIGNATURE_LIST a vrátí pole X509Certificate2 (pouze X.509 položky).
# Formát: opakující se hlavičky SignatureList, každá obsahuje seznam SignatureEntry
# s 16B GUID prefixem (SignatureOwner) + DER-encoded certifikát.
param([byte[]]$Bytes)
$certs = @()
if (-not $Bytes -or $Bytes.Length -lt 28) { return $certs }
# EFI_CERT_X509_GUID: {a5c059a1-94e4-4aa7-87b5-ab155c2bf072} (little-endian)
$x509Guid = [byte[]](0xa1,0x59,0xc0,0xa5,0xe4,0x94,0xa7,0x4a,0x87,0xb5,0xab,0x15,0x5c,0x2b,0xf0,0x72)
$pos = 0
while ($pos + 28 -le $Bytes.Length) {
$sigTypeGuid = $Bytes[$pos..($pos+15)]
$listSize = [BitConverter]::ToUInt32($Bytes, $pos+16)
$headerSize = [BitConverter]::ToUInt32($Bytes, $pos+20)
$sigSize = [BitConverter]::ToUInt32($Bytes, $pos+24)
if ($listSize -lt 28 -or $listSize -gt ($Bytes.Length - $pos)) { break }
$isX509 = $true
for ($i = 0; $i -lt 16; $i++) {
if ($sigTypeGuid[$i] -ne $x509Guid[$i]) { $isX509 = $false; break }
}
if ($isX509 -and $sigSize -gt 16) {
$entryPos = $pos + 28 + $headerSize
$listEnd = $pos + $listSize
while ($entryPos + $sigSize -le $listEnd) {
$certOffset = $entryPos + 16 # přeskočit 16B SignatureOwner GUID
$certSize = [int]$sigSize - 16
if ($certOffset + $certSize -le $Bytes.Length -and $certSize -gt 0) {
try {
$certBytes = [byte[]]($Bytes[$certOffset..($certOffset + $certSize - 1)])
$certs += New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(, $certBytes)
} catch { }
}
$entryPos += $sigSize
}
}
$pos += $listSize
}
return $certs
}
function Convert-CertToInfo {
param($Cert)
[ordered]@{
Subject = $Cert.Subject
Thumbprint = $Cert.Thumbprint
NotAfter = $Cert.NotAfter.ToString('yyyy-MM-dd')
}
}
function Get-SbVarAscii {
param([ValidateSet('PK','KEK','db','dbx')][string]$Name)
try {
return [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI -Name $Name -ErrorAction Stop).Bytes)
} catch { return $null }
}
function Get-CertificateStatus {
$st = [ordered]@{
KEK = [ordered]@{
Has2011 = $false; Has2023 = $false
Certs2011 = @(); Certs2023 = @()
Error = $null
}
DB = [ordered]@{
Has2011UEFI = $false; Has2011WindowsPCA = $false
Has2023UEFI = $false; Has2023OptionROM = $false
Has2023WindowsUEFI = $false
Certs2011 = @(); Certs2023 = @()
Error = $null
}
DbxRevokesPCA2011 = $false
AnyExpiring2011 = $false
}
try {
$kekBytes = (Get-SecureBootUEFI -Name KEK -ErrorAction Stop).Bytes
foreach ($cert in (Parse-EFISignatureList -Bytes $kekBytes)) {
$subj = $cert.Subject
$info = Convert-CertToInfo $cert
if ($subj -like '*KEK CA 2011*') {
$st.KEK.Has2011 = $true; $st.KEK.Certs2011 += $info
}
if ($subj -like "*$CN_KEK2023*" -or $subj -like '*KEK*CA 2023*') {
$st.KEK.Has2023 = $true; $st.KEK.Certs2023 += $info
}
}
} catch { $st.KEK.Error = $_.Exception.Message }
try {
$dbBytes = (Get-SecureBootUEFI -Name db -ErrorAction Stop).Bytes
foreach ($cert in (Parse-EFISignatureList -Bytes $dbBytes)) {
$subj = $cert.Subject
$info = Convert-CertToInfo $cert
if ($subj -like '*UEFI CA 2011*') {
$st.DB.Has2011UEFI = $true; $st.DB.Certs2011 += $info
}
if ($subj -like '*Windows Production PCA 2011*' -or $subj -like '*Windows PCA 2011*') {
$st.DB.Has2011WindowsPCA = $true; $st.DB.Certs2011 += $info
}
if ($subj -like "*$CN_UEFI2023*" -and $subj -notlike '*Option ROM*' -and $subj -notlike '*Windows UEFI*') {
$st.DB.Has2023UEFI = $true; $st.DB.Certs2023 += $info
}
if ($subj -like "*$CN_OPTROM2023*") {
$st.DB.Has2023OptionROM = $true; $st.DB.Certs2023 += $info
}
if ($subj -like "*$CN_WINUEFI2023*") {
$st.DB.Has2023WindowsUEFI = $true; $st.DB.Certs2023 += $info
}
}
} catch { $st.DB.Error = $_.Exception.Message }
# ASCII fallback — doplní booleany pokud X.509 parse selhal
$kAscii = Get-SbVarAscii KEK
if ($kAscii -and $kAscii -match [regex]::Escape($CN_KEK2023)) { $st.KEK.Has2023 = $true }
$dAscii = Get-SbVarAscii db
if ($dAscii) {
if ($dAscii -match [regex]::Escape($CN_WINUEFI2023)) { $st.DB.Has2023WindowsUEFI = $true }
if ($dAscii -match [regex]::Escape($CN_OPTROM2023)) { $st.DB.Has2023OptionROM = $true }
if ($dAscii -match 'Microsoft UEFI CA 2023') { $st.DB.Has2023UEFI = $true }
}
$xAscii = Get-SbVarAscii dbx
if ($xAscii -and $xAscii -match [regex]::Escape($CN_PCA2011)) { $st.DbxRevokesPCA2011 = $true }
$st.AnyExpiring2011 = $st.KEK.Has2011 -or $st.DB.Has2011UEFI -or $st.DB.Has2011WindowsPCA
return $st
}
function Get-RegistryStatus {
$reg = [ordered]@{
AvailableUpdates = $null
HighConfidenceOptOut = $null
MicrosoftUpdateManagedOptIn = $null
ServicingKeyExists = $false
UEFICA2023Status = $null
UEFICA2023StatusText = 'KeyNotPresent'
UEFICA2023Error = $null
UEFICA2023ErrorEvent = $null
WindowsUEFICA2023Capable = $null
WindowsUEFICA2023CapableText = '-'
ConfidenceLevel = $null
}
$main = Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue
if ($main) {
$reg.AvailableUpdates = $main.AvailableUpdates
$reg.HighConfidenceOptOut = $main.HighConfidenceOptOut
$reg.MicrosoftUpdateManagedOptIn = $main.MicrosoftUpdateManagedOptIn
}
$serv = Get-ItemProperty $REG_SERVICING -ErrorAction SilentlyContinue
if ($serv) {
$reg.ServicingKeyExists = $true
$reg.UEFICA2023Status = $serv.UEFICA2023Status
$reg.UEFICA2023Error = $serv.UEFICA2023Error
$reg.UEFICA2023ErrorEvent = $serv.UEFICA2023ErrorEvent
$reg.WindowsUEFICA2023Capable = $serv.WindowsUEFICA2023Capable
$reg.ConfidenceLevel = $serv.ConfidenceLevel
}
# UEFICA2023Status je REG_SZ (NotStarted/InProgress/Updated); defensivně i číselná varianta
$sv = $reg.UEFICA2023Status
if ($null -eq $sv) {
$reg.UEFICA2023StatusText = 'KeyNotPresent'
} elseif ($sv -is [string] -and $sv -ne '') {
$reg.UEFICA2023StatusText = $sv
} else {
$reg.UEFICA2023StatusText = switch ([int]$sv) {
0 { 'NotStarted' } 1 { 'InProgress' } 2 { 'Updated' } 3 { 'Failed' } default { "($sv)" }
}
}
$reg.WindowsUEFICA2023CapableText = switch ($reg.WindowsUEFICA2023Capable) {
0 { '0 — Windows UEFI CA 2023 není v DB' }
1 { '1 — certifikát v DB (boot manager zatím ne)' }
2 { '2 — bootuje se z 2023 boot manageru' }
$null { '-' }
default { [string]$reg.WindowsUEFICA2023Capable }
}
return $reg
}
function Get-AvailableUpdatesText {
param($v)
if ($null -eq $v) { return '(nenastaveno)' }
$iv = [int]$v
$hex = '0x{0:X}' -f $iv
$name = switch ($iv) {
0 { 'vše hotovo (proces dokončen)' }
0x4000 { 'vše aplikováno — task čistí guard bit' }
0x4100 { 'Boot Manager 2023 nasazen na ESP — čeká RESTART' }
0x4104 { 'Microsoft UEFI CA 2023 v DB' }
0x5104 { 'Option ROM UEFI CA 2023 v DB' }
0x5904 { 'Windows UEFI CA 2023 v DB' }
0x5944 { 'naplánována plná sada (start)' }
default { 'probíhá aplikace' }
}
return "$hex ($name)"
}
function Get-EventLogStatus {
$e = [ordered]@{
LastEventId = $null
LastEventTime = $null
RelevantEvents = @()
ById = @{}
Error = $null
}
$ids = @(1795,1796,1799,1800,1801,1802,1803,1808)
try {
$events = Get-WinEvent -FilterHashtable @{ LogName='System'; Id=$ids } -MaxEvents 40 -ErrorAction Stop
if ($events) {
$sorted = $events | Sort-Object TimeCreated -Descending
foreach ($id in $ids) {
$hit = $sorted | Where-Object { $_.Id -eq $id } | Select-Object -First 1
if ($hit) { $e.ById[$id] = @{ Time = $hit.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') } }
}
foreach ($ev in ($sorted | Select-Object -First 10)) {
$e.RelevantEvents += [ordered]@{
EventId = $ev.Id
TimeCreated = $ev.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
Level = $ev.LevelDisplayName
}
}
$last = $sorted | Select-Object -First 1
$e.LastEventId = $last.Id
$e.LastEventTime = $last.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
}
} catch {
if ($_.CategoryInfo.Reason -ne 'NoMatchingEventsException') { $e.Error = $_.Exception.Message }
}
return $e
}
function Get-TaskExists {
try {
$null = Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction Stop
return $true
} catch { return $false }
}
#endregion
#region ── Kategorizace ───────────────────────────────────────────────────────
function Test-RemediationComplete {
# Ověří všechny podmínky úspěchu: 4 registry + ověření bootloaderu.
# Boot Manager ověření: Capable=2 (registry) NEBO podpis souboru (Is2023).
param($R)
$cert = $R.Certificates
$reg = $R.Registry
$bm = $R.BootManager
$certsOk = $cert.KEK.Has2023 -and $cert.DB.Has2023WindowsUEFI
$auVal = [int]$reg.AvailableUpdates
$auOk = ($null -ne $reg.AvailableUpdates) -and ($auVal -eq 0 -or $auVal -eq 0x4000)
$statusOk = $reg.UEFICA2023StatusText -eq 'Updated'
$errorOk = ($null -eq $reg.UEFICA2023Error) -or ([int]$reg.UEFICA2023Error -eq 0)
$bmOk = ($reg.WindowsUEFICA2023Capable -eq 2) -or ($bm -and $bm.Checked -and $bm.Is2023)
return $certsOk -and $auOk -and $statusOk -and $errorOk -and $bmOk
}
function Get-RemediationCategory {
param($Result)
$sb = $Result.SecureBoot
$reg = $Result.Registry
$evt = $Result.EventLog
$cert = $Result.Certificates
$env = $Result.EnvironmentType
$mode = $Result.OperatingMode
# ── Základní blokátory ────────────────────────────────────────────────────
if (-not $sb.IsUEFI -or -not $sb.IsSupported) {
if ($env -like '*VM*') {
return @{ Code='NO_SECUREBOOT_VM'; Color='DarkGray'
Label='Secure Boot nepodporováno — VM bez vTPM nebo UEFI' }
}
return @{ Code='NO_SECUREBOOT'; Color='DarkGray'
Label='Secure Boot nepodporováno — Legacy BIOS' }
}
if (-not $sb.IsEnabled) {
return @{ Code='SECUREBOOT_DISABLED'; Color='Yellow'
Label='Secure Boot je vypnutý' }
}
if ($mode -eq 'Setup') {
return @{ Code='SETUP_MODE'; Color='Magenta'
Label='Secure Boot v Setup Mode — aktualizaci nelze dokončit (chybí enrolled PK)' }
}
if (-not $Result.TaskExists) {
return @{ Code='TASK_MISSING'; Color='Magenta'
Label='Chybí servicing task — nainstalujte kumulativní update min. z 10/2025 (KB5066835)' }
}
# ── Chyby a blokující stavy ───────────────────────────────────────────────
$hasErrorEvent = $reg.UEFICA2023ErrorEvent -and ([int]$reg.UEFICA2023ErrorEvent -ne 0)
if ($evt.ById[1795] -or $hasErrorEvent -or $reg.UEFICA2023StatusText -eq 'Failed') {
return @{ Code='UPDATE_FAILED'; Color='Red'
Label='Selhání aktualizace — chyba firmwaru nebo UEFICA2023ErrorEvent' }
}
if ($reg.ConfidenceLevel -like 'Not Supported*') {
return @{ Code='NOT_SUPPORTED'; Color='Red'
Label='Zařízení nepodporuje automatickou aktualizaci (ConfidenceLevel: Not Supported)' }
}
if ($reg.ConfidenceLevel -like 'Temporarily Paused*' -or $evt.ById[1802]) {
return @{ Code='FIRMWARE_UPDATE_NEEDED'; Color='Magenta'
Label='Aktualizace pozastavena — problém firmwaru, zkontrolujte update u OEM' }
}
# ── Hotovo (registry + bootloader ověřen) ─────────────────────────────────
if (Test-RemediationComplete -R $Result) {
$bm = $Result.BootManager
$bmReason = if ($reg.WindowsUEFICA2023Capable -eq 2) { 'Capable=2' } `
elseif ($bm -and $bm.Checked -and $bm.Is2023) { 'soubor ověřen' } `
else { '' }
$doneLabel = if ($bmReason) { "HOTOVO — 2023 certifikáty + bootloader ověřen ($bmReason)" } `
else { 'HOTOVO — 2023 certifikáty nasazeny' }
if ($cert.AnyExpiring2011) {
return @{ Code='OK_TRANSITION'; Color='Green'
Label="$doneLabel (staré 2011 ještě přítomné, je to normální)" }
}
return @{ Code='OK'; Color='Green'; Label=$doneLabel }
}
# ── Boot Manager staged, čeká na restart ─────────────────────────────────
if ([int]$reg.AvailableUpdates -eq 0x4100) {
return @{ Code='UPDATE_PENDING_RESTART'; Color='Yellow'
Label='Boot Manager 2023 nasazen (staged) — čeká na RESTART' }
}
$certsRequiredOk = $cert.KEK.Has2023 -and $cert.DB.Has2023WindowsUEFI
$auNow = [int]$reg.AvailableUpdates
$errOk = ($null -eq $reg.UEFICA2023Error) -or ([int]$reg.UEFICA2023Error -eq 0)
$bm = $Result.BootManager
$bmOk = ($reg.WindowsUEFICA2023Capable -eq 2) -or ($bm -and $bm.Checked -and $bm.Is2023)
# ── Certy přítomny + AU=0x0 + bootloader ověřen → hotovo ─────────────────
# Poznámka: null AU (chybějící Servicing klíč) se neuznává jako hotovo.
if ($certsRequiredOk -and ($null -ne $reg.AvailableUpdates) -and $auNow -eq 0 -and $errOk -and $bmOk) {
$bmReason = if ($reg.WindowsUEFICA2023Capable -eq 2) { 'Capable=2' } `
elseif ($bm -and $bm.Checked -and $bm.Is2023) { 'soubor ověřen' } `
else { '' }
$doneLabel = if ($bmReason) { "Certifikáty aplikovány — bootloader ověřen ($bmReason)" } `
else { 'Certifikáty aplikovány — AvailableUpdates=0x0' }
if ($cert.AnyExpiring2011) {
return @{ Code='OK_TRANSITION'; Color='Green'
Label="$doneLabel (staré 2011 ještě přítomné)" }
}
return @{ Code='OK'; Color='Green'; Label=$doneLabel }
}
# ── Certy přítomny, AU=0x0, ale bootloader dosud nepoužívá 2023 CA ───────
# Systém potřebuje spustit task znovu → staged BM 2023 → restart.
if ($certsRequiredOk -and ($null -ne $reg.AvailableUpdates) -and $auNow -eq 0 -and $errOk -and -not $bmOk) {
$bmNote = if ($bm -and $bm.Checked -and $bm.Is2011) {
"soubor podepsán starší CA: $($bm.IssuerCA)"
} elseif ($bm -and $bm.Error) {
"soubor: $($bm.Error)"
} else {
"Capable=$($reg.WindowsUEFICA2023Capable)"
}
return @{ Code='UPDATE_BOOTMANAGER'; Color='Yellow'
Label="Certifikáty v DB/KEK — bootloader dosud nepoužívá 2023 CA ($bmNote)" }
}
# ── Starý build: certy přítomny, ale servicing infrastruktura chybí ──────
$certsPresent = $cert.KEK.Has2023 -or $cert.DB.Has2023WindowsUEFI
$bmStagedAU = ($auNow -eq 0x4100 -or $auNow -eq 0x4000)
if ($certsPresent -and -not $bmStagedAU `
-and ($null -eq $reg.WindowsUEFICA2023Capable) `
-and ($null -eq $reg.UEFICA2023Status)) {
return @{ Code='BUILD_OUTDATED'; Color='Magenta'
Label='Build příliš starý — chybí servicing pro Boot Manager 2023, nutný Windows Update' }
}
# ── Částečný postup / nutná aktualizace ──────────────────────────────────
if ($certsPresent) {
return @{ Code='UPDATE_PARTIAL'; Color='Cyan'
Label='Aktualizace probíhá — certifikáty nasazeny, zbývá dokončit servisování' }
}
return @{ Code='UPDATE_NEEDED'; Color='Yellow'
Label='Povinné certifikáty 2023 nejsou přítomny — nutná aktualizace' }
}
#endregion
#region ── Detekce bootloaderu ────────────────────────────────────────────────
function Get-EspBootmgfwCopy {
# Zkopíruje bootmgfw.efi z aktivního ESP do %TEMP% a vrátí výsledek.
# Logika převzata z referenčního scriptu (mathisokle/SecureBoot-CA2023-Automatic-Update).
# Nikdy nepracuje přímo s live souborem — vždy nejprve kopie.
$out = @{ Success=$false; TempPath=$null; Source=$null; Error=$null }
$tempDir = Join-Path $env:TEMP 'SecureBootCA2023'
if (-not (Test-Path -LiteralPath $tempDir)) {
New-Item -Path $tempDir -ItemType Directory -Force | Out-Null
}
$dst = Join-Path $tempDir ('bootmgfw_{0}_{1}.efi' -f $env:COMPUTERNAME, ([guid]::NewGuid().ToString('N')))
$dl = $EspDriveLetter.ToUpper()
$driveRoot = "${dl}:"
# Pokus 1: ESP má přiřazené písmeno — prohledat všechna písmena AZ
foreach ($d in [char[]]('A'..'Z')) {
$p = "${d}:\EFI\Microsoft\Boot\bootmgfw.efi"
if (Test-Path -LiteralPath $p -ErrorAction SilentlyContinue) {
try {
Copy-Item -LiteralPath $p -Destination $dst -Force -ErrorAction Stop
$out.Success = $true; $out.TempPath = $dst; $out.Source = $p
return $out
} catch { $out.Error = $_.Exception.Message }
}
}
# Pokus 2: mountvol $EspDriveLetter: /S
# Připojí aktivní EFI System Partition na zadané písmeno (default S:).
# Pokud je písmeno obsazeno, změní je parametrem -EspDriveLetter.
if (-not (Get-PSDrive -Name $dl -ErrorAction SilentlyContinue)) {
Add-LogLine ("ESP mount: mountvol ${driveRoot} /S")
& mountvol $driveRoot /S 2>&1 | Out-Null
Start-Sleep -Seconds 2
}
if (-not (Test-Path -LiteralPath "${driveRoot}\" -ErrorAction SilentlyContinue)) {
$out.Error = "Nepodařilo se připojit ESP na ${driveRoot} — zkuste jiné písmeno parametrem -EspDriveLetter"
return $out
}
try {
$src = "${driveRoot}\EFI\Microsoft\Boot\bootmgfw.efi"
if (-not (Test-Path -LiteralPath $src -ErrorAction SilentlyContinue)) {
$out.Error = "bootmgfw.efi nenalezen na ${driveRoot} (ESP připojen, ale soubor chybí)"
return $out
}
Add-LogLine ("ESP copy: ${src}${dst}")
Copy-Item -LiteralPath $src -Destination $dst -Force -ErrorAction Stop
$out.Success = $true; $out.TempPath = $dst; $out.Source = $src
} catch {
$out.Error = "Chyba kopírování z ESP: $($_.Exception.Message)"
} finally {
if (Get-PSDrive -Name $dl -ErrorAction SilentlyContinue) {
Add-LogLine ("ESP dismount: mountvol ${driveRoot} /D")
& mountvol $driveRoot /D 2>&1 | Out-Null
}
}
return $out
}
function Get-BootManagerEvidence {
# Ověří CA verzi Boot Manageru (bootmgfw.efi) třemi vrstvami důkazů:
# 1. certutil -dump — čte embedded PKCS#7 blob přímo z PE, bez local cert store
# 2. X509Chain — záloha pokud certutil chybí (závisí na local store)
# 3. Event ID 1799 — "Boot Manager signed with Windows UEFI CA 2023 was installed"
#
# Vrátí hashtable s: Checked, Is2023, Is2011, CAVersion, IssuerCA,
# SignerSubject, Source, Method, Error
$out = [ordered]@{
Checked = $false # podařilo se ověřit?
Is2023 = $false # bootloader podepsán 2023 CA?
Is2011 = $false # bootloader podepsán 2011 CA?
CAVersion = $null # '2023', '2011', nebo $null
IssuerCA = $null # název CA (krátký)
SignerSubject = $null # leaf cert Subject z AuthSig (pro log)
Source = $null # původní cesta na ESP
Method = $null # 'certutil', 'x509chain', 'event1799'
Error = $null # chybová zpráva
}
# ── Krok 1: Zkopírovat bootmgfw.efi z aktivního ESP ───────────────────────
$espCopy = Get-EspBootmgfwCopy
if (-not $espCopy.Success) {
$out.Error = $espCopy.Error
} else {
$out.Source = $espCopy.Source
$file = $espCopy.TempPath
try {
# ── Krok 2a: certutil -dump (primární) ────────────────────────────
$certutil = Get-Command certutil.exe -ErrorAction SilentlyContinue
if ($certutil) {
$raw = & certutil.exe -dump $file 2>&1 | Out-String
$out.Checked = $true
$out.Method = 'certutil'
if ($raw -match 'Windows Production PCA 2023') {
$out.Is2023 = $true; $out.CAVersion = '2023'
$out.IssuerCA = 'Windows Production PCA 2023'
} elseif ($raw -match 'Windows UEFI CA 2023') {
$out.Is2023 = $true; $out.CAVersion = '2023'
$out.IssuerCA = 'Windows UEFI CA 2023'
} elseif ($raw -match 'Windows Production PCA 2011') {
$out.Is2011 = $true; $out.CAVersion = '2011'
$out.IssuerCA = 'Windows Production PCA 2011'
} elseif ($raw -match 'Windows UEFI CA 2011') {
$out.Is2011 = $true; $out.CAVersion = '2011'
$out.IssuerCA = 'Windows UEFI CA 2011'
}
# Pokud certutil nenašel žádnou CA → out.CAVersion zůstane $null
}
# ── Krok 2b: Get-AuthenticodeSignature ────────────────────────────
# Vždy — pro SignerSubject do logu. Když certutil nebyl dostupný,
# X509Chain slouží jako záloha pro Is2023/Is2011.
try {
$sig = Get-AuthenticodeSignature -FilePath $file -ErrorAction Stop
if ($sig.SignerCertificate) {
$out.SignerSubject = $sig.SignerCertificate.Subject
if (-not $certutil) {
# X509Chain fallback — závisí na local cert store
$out.Checked = $true; $out.Method = 'x509chain'
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.ChainPolicy.RevocationMode = `
[System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck
$chain.Build($sig.SignerCertificate) | Out-Null
foreach ($el in $chain.ChainElements) {
$s = $el.Certificate.Subject
if ($s -like '*Windows UEFI CA 2023*' -or $s -like '*Windows Production PCA 2023*') {
$out.Is2023 = $true; $out.CAVersion = '2023'
$out.IssuerCA = ($s -replace '^CN=([^,]+).*','$1').Trim()
break
}
}
if (-not $out.Is2023) {
foreach ($el in $chain.ChainElements) {
$s = $el.Certificate.Subject
if ($s -like '*Windows Production PCA 2011*' -or $s -like '*Windows UEFI CA 2011*') {
$out.Is2011 = $true; $out.CAVersion = '2011'
$out.IssuerCA = ($s -replace '^CN=([^,]+).*','$1').Trim()
break
}
}
}
}
}
} catch { } # AuthSig je doplňkový — chybu ignorujeme
} catch {
$out.Error = $_.Exception.Message
} finally {
Remove-Item $file -Force -ErrorAction SilentlyContinue
}
}
# ── Krok 3: Event ID 1799 — terciální evidence ────────────────────────────
# Zpráva "Boot Manager signed with Windows UEFI CA 2023 was installed successfully"
# Používá se jen jako podpora pokud certutil/chain ještě neprokázaly 2023.
if (-not $out.Is2023) {
try {
$ev1799 = Get-WinEvent -FilterHashtable @{ LogName='System'; Id=1799 } `
-MaxEvents 5 -ErrorAction SilentlyContinue |
Where-Object { $_.Message -match 'Windows UEFI CA 2023.*installed successfully' } |
Select-Object -First 1
if ($ev1799) {
$out.Is2023 = $true
$out.CAVersion = '2023'
$out.IssuerCA = 'Windows UEFI CA 2023'
$out.Method = if ($out.Method) { "$($out.Method)+event1799" } else { 'event1799' }
$out.Checked = $true
}
} catch { }
}
return $out
}
#endregion
#region ── Detekce — orchestrace ──────────────────────────────────────────────
function Invoke-Detection {
$os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
$osBuildFull = try {
$cv = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction Stop
'{0}.{1}' -f $cv.CurrentBuildNumber, $cv.UBR
} catch {
if ($os) { [string]$os.BuildNumber } else { '?' }
}
$R = [ordered]@{
AuditTimestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
Hostname = $env:COMPUTERNAME
OSCaption = if ($os) { $os.Caption } else { $null }
OSBuildFull = $osBuildFull
EnvironmentType = Get-EnvironmentType
Hardware = Get-HardwareInfo
SecureBoot = Get-SecureBootState
OperatingMode = 'Unknown'
BitLocker = $null
Certificates = $null
BootManager = $null
Registry = Get-RegistryStatus
EventLog = Get-EventLogStatus
TaskExists = Get-TaskExists
Category = $null
CategoryLabel = $null
CategoryColor = $null
}
if ($R.SecureBoot.IsUEFI -and $R.SecureBoot.IsSupported) {
$R.OperatingMode = Get-SecureBootMode
$R.Certificates = Get-CertificateStatus
# Verifikace Boot Manageru (certutil + X509Chain + Event 1799)
$R.BootManager = Get-BootManagerEvidence
} else {
$R.Certificates = [ordered]@{
KEK = [ordered]@{
Has2011 = $false; Has2023 = $false
Certs2011 = @(); Certs2023 = @()
Error = 'Secure Boot nedostupný'
}
DB = [ordered]@{
Has2011UEFI = $false; Has2011WindowsPCA = $false
Has2023UEFI = $false; Has2023OptionROM = $false
Has2023WindowsUEFI = $false
Certs2011 = @(); Certs2023 = @()
Error = 'Secure Boot nedostupný'
}
DbxRevokesPCA2011 = $false
AnyExpiring2011 = $false
}
$R.BootManager = @{
Checked = $false; Is2023 = $false; Is2011 = $false
CAVersion = $null; IssuerCA = $null; SignerSubject = $null
Source = $null; Method = $null; Error = 'Secure Boot nedostupný'
}
}
$R.BitLocker = Get-BitLockerInfo
$cat = Get-RemediationCategory -Result $R
$R.Category = $cat.Code
$R.CategoryLabel = $cat.Label
$R.CategoryColor = $cat.Color
return $R
}
#endregion
#region ── Zobrazení ──────────────────────────────────────────────────────────
function Show-Prerequisites {
param($Prereqs)
Write-Head 'PŘEDPOKLADY'
$i = 1
foreach ($p in $Prereqs) {
$state = if ($p.Ok) { 'Done' } elseif ($p.Hard) { 'Fail' } else { 'Info' }
$nc = if ($p.Ok) { 'DarkGray' } elseif ($p.Hard) { 'Red' } else { 'Yellow' }
Write-Check -State $state -Text ("{0}. {1}" -f $i, $p.Label) -Note $p.Note -NoteColor $nc
$i++
}
$hardFails = @($Prereqs | Where-Object { $_.Hard -and -not $_.Ok })
if ($hardFails.Count) {
Write-Host ''
Write-Host ' ! Před spuštěním remediace vyřešte výše označené problémy.' -ForegroundColor Red
} else {
Write-Host ''
Write-Host ' Všechny předpoklady splněny.' -ForegroundColor Green
}
}
function Show-Certificates {
param($Cert, $BM)
Write-Head 'CERTIFIKÁTY'
# Najdi expiry datum pro přítomné povinné certifikáty
$winUefiExpiry = $null
$kekExpiry = $null
foreach ($c in $Cert.DB.Certs2023) {
if ($c.Subject -like "*$CN_WINUEFI2023*" -and -not $winUefiExpiry) { $winUefiExpiry = $c.NotAfter }
}
foreach ($c in $Cert.KEK.Certs2023) {
if ($c.Subject -like "*$CN_KEK2023*" -and -not $kekExpiry) { $kekExpiry = $c.NotAfter }
}
$items = @(
@{ Label='Windows UEFI CA 2023'; Present=$Cert.DB.Has2023WindowsUEFI; Required=$true; NotAfter=$winUefiExpiry }
@{ Label='Microsoft Corporation KEK 2K CA 2023'; Present=$Cert.KEK.Has2023; Required=$true; NotAfter=$kekExpiry }
@{ Label='Microsoft UEFI CA 2023'; Present=$Cert.DB.Has2023UEFI; Required=$false; NotAfter=$null }
@{ Label='Microsoft Option ROM UEFI CA 2023'; Present=$Cert.DB.Has2023OptionROM; Required=$false; NotAfter=$null }
)
foreach ($item in $items) {
$tag = if ($item.Required) { '(povinný) ' } else { '(volitelný)' }
$expiry = if ($item.NotAfter) { " platný do $($item.NotAfter)" } else { '' }
if ($item.Present) {
Write-Host (" [{0}] {1,-44} {2}{3}" -f $SYM_DONE, $item.Label, $tag, $expiry) -ForegroundColor Green
Add-LogLine ("[+] $($item.Label) — přítomen$expiry")
} elseif ($item.Required) {
Write-Host (" [{0}] {1,-44} {2}" -f $SYM_FAIL, $item.Label, $tag) -ForegroundColor Red
Add-LogLine ("[!] $($item.Label) — CHYBÍ (povinný)")
} else {
Write-Host (" [{0}] {1,-44} {2}" -f $SYM_PENDING, $item.Label, $tag) -ForegroundColor DarkGray
Add-LogLine ("[ ] $($item.Label) — chybí (volitelný)")
}
}
# ── Boot Manager — verze CA z verifikace souboru ─────────────────────────
Write-Host ''
$bmLabel = 'Boot Manager (bootmgfw.efi)'
$bmMethod = if ($BM -and $BM.Method) { " [$($BM.Method)]" } else { '' }
if ($BM -and $BM.Is2023) {
$detail = "aktuální — CA: $($BM.IssuerCA)$bmMethod"
Write-Host (" [{0}] {1,-44} {2}" -f $SYM_DONE, $bmLabel, $detail) -ForegroundColor Green
Add-LogLine "[+] Boot Manager: $detail"
} elseif ($BM -and $BM.Is2011) {
$detail = "ZASTARALÝ — CA: $($BM.IssuerCA) → nutná aktualizace$bmMethod"
Write-Host (" [{0}] {1,-44} {2}" -f $SYM_FAIL, $bmLabel, $detail) -ForegroundColor Red
Add-LogLine "[!] Boot Manager: $detail"
} elseif ($BM -and $BM.Checked) {
# Ověření proběhlo ale CA nebyla identifikována
$signerShort = if ($BM.SignerSubject) {
($BM.SignerSubject -replace '^CN=([^,]+).*','$1').Trim()
} else { '?' }
$detail = "CA nezjištěna — signer: $signerShort$bmMethod"
Write-Host (" [{0}] {1,-44} {2}" -f $SYM_PENDING, $bmLabel, $detail) -ForegroundColor DarkGray
Add-LogLine "[ ] Boot Manager: $detail"
} elseif ($BM) {
$errNote = if ($BM.Error) { " ($($BM.Error))" } else { '' }
$detail = "nedostupný$errNote"
Write-Host (" [{0}] {1,-44} {2}" -f $SYM_PENDING, $bmLabel, $detail) -ForegroundColor DarkGray
Add-LogLine "[ ] Boot Manager: $detail"
}
}
function Show-Registry {
param($Reg)
Write-Head 'REGISTRY'
$auText = Get-AvailableUpdatesText $Reg.AvailableUpdates
$auVal = if ($null -ne $Reg.AvailableUpdates) { [int]$Reg.AvailableUpdates } else { -1 }
$auColor = if ($auVal -eq 0 -or $auVal -eq 0x4000) { 'Green' } else { 'Cyan' }
$stText = $Reg.UEFICA2023StatusText
$stColor = switch ($stText) {
'Updated' { 'Green' }
'Failed' { 'Red' }
'KeyNotPresent' { 'DarkGray' }
default { 'White' }
}
$errRaw = $Reg.UEFICA2023Error
$errOk = ($null -eq $errRaw) -or ([int]$errRaw -eq 0)
$errText = if ($errOk) { '(žádná)' } else { [string]$errRaw }
$errColor = if ($errOk) { 'DarkGray' } else { 'Red' }
$capText = $Reg.WindowsUEFICA2023CapableText
$capColor = if ($Reg.WindowsUEFICA2023Capable -eq 2) { 'Green' } else { 'White' }
Write-KV 'AvailableUpdates' $auText $auColor
Write-KV 'UEFICA2023Status' $stText $stColor
Write-KV 'UEFICA2023Error' $errText $errColor
Write-KV 'WindowsUEFICA2023Capable' $capText $capColor
}
function Show-Events {
param($Evt)
Write-Head 'UDÁLOSTI (System log — Secure Boot)'
if (-not $Evt.RelevantEvents -or $Evt.RelevantEvents.Count -eq 0) {
Write-Host ' (žádné relevantní události)' -ForegroundColor DarkGray
Add-LogLine 'Události: žádné relevantní'
return
}
foreach ($ev in $Evt.RelevantEvents) {
$evDesc = if ($EVENT_DESC.ContainsKey($ev.EventId)) { $EVENT_DESC[$ev.EventId] } else { $ev.Level }
$color = if ($ev.EventId -in @(1795,1796)) { 'Red' } `
elseif ($ev.EventId -eq 1808) { 'Green' } `
else { 'DarkGray' }
Write-Host (" {0} EventID {1,-4} {2}" -f $ev.TimeCreated, $ev.EventId, $evDesc) -ForegroundColor $color
Add-LogLine (" [{0}] EventID {1}: {2}" -f $ev.TimeCreated, $ev.EventId, $evDesc)
}
}
function Show-Status {
# Zobrazí všechny 4 sekce + celkový stav.
# STAV se nezobrazí, pokud některá hard prerekvizita selhala — ta musí být
# vyřešena dřív, než má kategorie (computed z neúplných dat) jakýkoliv smysl.
param($R, $Prereqs)
Show-Prerequisites -Prereqs $Prereqs
Show-Certificates -Cert $R.Certificates -BM $R.BootManager
Show-Registry -Reg $R.Registry
Show-Events -Evt $R.EventLog
$anyHardFail = [bool]@($Prereqs | Where-Object { $_.Hard -and -not $_.Ok }).Count
if (-not $anyHardFail) {
Write-Host ''
Write-Host ' STAV: ' -ForegroundColor Gray -NoNewline
Write-Host $R.CategoryLabel -ForegroundColor $R.CategoryColor
Add-LogLine ("STAV: {0}" -f $R.CategoryLabel)
}
}
function Show-TwoRestartBlock {
param($R)
$bl = $R.BitLocker
$blActive = $bl -and ($bl.Status -eq 'On' -or $bl.Status -eq '1' -or $bl.Status -eq '2')
Write-Host ''
Write-Host ' *** Boot Manager 2023 je připraven na disku (staged) ***' -ForegroundColor Yellow
Write-Host ''
Write-Host ' Pro dokončení jsou potřeba 2 kroky:' -ForegroundColor White
Write-Host ' 1. RESTART — aktivuje nový Boot Manager' -ForegroundColor Gray
Write-Host ' 2. Windows automaticky dokončí zbývající kroky po restartu' -ForegroundColor Gray
Write-Host ''
if ($blActive -and $bl.UsesPcr) {
Write-Host ' BitLocker: Disk je chráněn s TPM+PCR7.' -ForegroundColor Yellow
Write-Host ' Před restartem si připravte BitLocker recovery key!' -ForegroundColor Yellow
} else {
Write-Host ' BitLocker: Před restartem ověřte dostupnost recovery key.' -ForegroundColor Gray
}
Add-LogLine 'AU=0x4100 — zobrazen blok "2 kroky"'
}
#endregion
#region ── Souhlas + restart ──────────────────────────────────────────────────
function Get-UserConsent {
param([string]$Question, [string]$Detail)
if ($AssumeYes) {
Write-Line (" {0} -> automaticky ANO (-AssumeYes)" -f $Question) Cyan
return $true
}
if (-not [Environment]::UserInteractive) {
Write-Line ' Neinteraktivní relace bez -AssumeYes — remediace se neprovede.' Yellow
return $false
}
Write-Host ''
Write-Host $Question -ForegroundColor White
if ($Detail) { Write-Host $Detail -ForegroundColor DarkGray }
try {
$yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Ano', 'Aplikovat (bez restartu)'
$no = New-Object System.Management.Automation.Host.ChoiceDescription '&Ne', 'Neprovádět změny'
return ($Host.UI.PromptForChoice('', 'Vaše volba:', [System.Management.Automation.Host.ChoiceDescription[]]@($yes,$no), 1) -eq 0)
} catch {
return ((Read-Host 'Aplikovat? [a/N]') -match '^(a|ano|y|yes)$')
}
}
function Invoke-RestartOffer {
param([bool]$Auto)
if ($Auto) {
Write-Host ''
Write-Host ' AutoRestart: restartuji server nyní...' -ForegroundColor Yellow
Add-LogLine 'Restart: -AutoRestart — spouštím Restart-Computer'
Flush-Log
Restart-Computer -Force
return
}
if (-not [Environment]::UserInteractive) {
Write-Line ' (Neinteraktivní relace — restart proveďte ručně.)' DarkGray
return
}
Write-Host ''
Write-Host ' Chcete nyní restartovat server? (výchozí: Ne)' -ForegroundColor White
try {
$yn = New-Object System.Management.Automation.Host.ChoiceDescription '&Ano', 'Restartovat server nyní'
$nn = New-Object System.Management.Automation.Host.ChoiceDescription '&Ne', 'Restartovat ručně'
if ($Host.UI.PromptForChoice('', ' Restart:', [System.Management.Automation.Host.ChoiceDescription[]]@($yn,$nn), 1) -eq 0) {
Add-LogLine 'Restart: uživatel potvrdil — spouštím Restart-Computer'
Flush-Log
Restart-Computer -Force
} else {
Write-Host ' Restart odložen — restartujte server ručně co nejdříve.' -ForegroundColor Gray
Add-LogLine 'Restart: uživatel odložil restart'
}
} catch {
if ((Read-Host ' Restartovat nyní? [a/N]') -match '^(a|ano|y|yes)$') {
Add-LogLine 'Restart: uživatel potvrdil restart (fallback Read-Host)'
Flush-Log
Restart-Computer -Force
} else {
Write-Line ' Restart odložen — restartujte server ručně co nejdříve.' Gray
Add-LogLine 'Restart: uživatel odložil restart (fallback)'
}
}
}
#endregion
#region ── Předpoklady ────────────────────────────────────────────────────────
function Get-Prerequisites {
param($R, [bool]$IsAdmin)
$sb = $R.SecureBoot
$list = @()
# 1. Administrátorská práva
$list += @{
Label = 'Administrátorská práva'
Ok = $IsAdmin; Hard = $true
Note = if (-not $IsAdmin) { 'Spusťte PowerShell "Run as administrator"' } else { $null }
}
# 2. Windows Server
$isServer = $R.OSCaption -like '*Windows Server*'
$list += @{
Label = ("Windows Server (2016/2019/2022/2025) — Zjištěno: {0} (build {1})" -f $R.OSCaption, $R.OSBuildFull)
Ok = $isServer; Hard = $true
Note = if (-not $isServer) { 'Skript je určen pro Windows Server' } else { $null }
}
# 3. UEFI + Secure Boot zapnutý
$sbAllOk = $sb.IsUEFI -and $sb.IsSupported -and $sb.IsEnabled
$sbNote = if (-not $sb.IsUEFI) { 'Legacy BIOS — Secure Boot není k dispozici' } `
elseif (-not $sb.IsSupported) { 'Secure Boot není podporováno' } `
elseif (-not $sb.IsEnabled) { 'Secure Boot je vypnutý — zapněte v UEFI/BIOS' } `
else { $null }
$list += @{
Label = 'UEFI + Secure Boot zapnutý'
Ok = $sbAllOk; Hard = $true; Note = $sbNote
}
# 4. Stavové registry klíče přítomny (servicing klíč = záplata >= 10/2025)
$servNote = if (-not $R.Registry.ServicingKeyExists) {
"build $($R.OSBuildFull) — nainstalujte kumulativní update min. z 10/2025 (KB5066835)"
} else { $null }
$list += @{
Label = 'Úroveň záplat >= 10/2025 — stavové registry klíče přítomny'
Ok = $R.Registry.ServicingKeyExists; Hard = $true; Note = $servNote
}
# 5. Scheduled task Secure-Boot-Update
$taskNote = if (-not $R.TaskExists) { "Task '$TASK_NAME' chybí — nainstalujte kumulativní update min. z 10/2025" } else { $null }
$list += @{
Label = ("Scheduled task '$TASK_NAME' k dispozici")
Ok = [bool]$R.TaskExists; Hard = $true; Note = $taskNote
}
# 6. PowerShell SecureBoot cmdlety
$sbCmdletOk = $null -ne (Get-Command Get-SecureBootUEFI -ErrorAction SilentlyContinue)
$list += @{
Label = 'PowerShell SecureBoot cmdlety — Get-SecureBootUEFI dostupný'
Ok = $sbCmdletOk; Hard = $true
Note = if (-not $sbCmdletOk) { 'Cmdlet nedostupný — možná Legacy BIOS nebo chybí modul' } else { $null }
}
return $list
}
#endregion
#region ── Remediace ──────────────────────────────────────────────────────────
function Invoke-Remediation {
$out = [ordered]@{ Status='Unknown'; Message=''; After=$null }
Write-Head 'REMEDIACE'
Add-LogLine 'REMEDIACE START'
# Krok 1/3: Nastavit registry
# AvailableUpdates = 0x5944 pouze jednou — při prvním spuštění (null) nebo po dokončení (0).
# Systém hodnotu sám spravuje: 0x5944→0x5904→0x5104→0x4104→0x4100→0x4000→0x0.
$auCurrent = (Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue).AvailableUpdates
$auNeedsInit = ($null -eq $auCurrent) -or ([int]$auCurrent -eq 0)
$auInitLabel = if ($auNeedsInit) { '0x5944 (první inicializace)' } else { '0x{0:X} (zachováno, probíhá)' -f [int]$auCurrent }
Write-Host (" [1/3] Nastavuji registry (AvailableUpdates={0}) ... " -f $auInitLabel) -NoNewline
try {
if (-not (Test-Path $REG_SECUREBOOT)) {
New-Item -Path $REG_SECUREBOOT -Force -ErrorAction Stop | Out-Null
}
Set-ItemProperty -Path $REG_SECUREBOOT -Name 'MicrosoftUpdateManagedOptIn' -Value 1 -Type DWord -Force -ErrorAction Stop
if ($auNeedsInit) {
Set-ItemProperty -Path $REG_SECUREBOOT -Name 'AvailableUpdates' -Value $AVAILABLE_UPDATES_VALUE -Type DWord -Force -ErrorAction Stop
$auCheck = (Get-ItemProperty $REG_SECUREBOOT -ErrorAction Stop).AvailableUpdates
if ([int]$auCheck -ne $AVAILABLE_UPDATES_VALUE) {
throw ('Ověření selhalo — AvailableUpdates=0x{0:X}, očekáváno 0x5944' -f [int]$auCheck)
}
}
$optOut = Get-ItemProperty $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -ErrorAction SilentlyContinue
if ($optOut -and $optOut.HighConfidenceOptOut -ne 0) {
Set-ItemProperty -Path $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -Value 0 -Type DWord -Force -ErrorAction Stop
}
if (-not (Test-Path $REG_SERVICING)) {
New-Item -Path $REG_SERVICING -Force -ErrorAction Stop | Out-Null
}
Write-Host 'OK' -ForegroundColor Green
Add-LogLine ("Krok 1: MicrosoftUpdateManagedOptIn=1, AvailableUpdates={0}" -f $auInitLabel)
} catch {
Write-Host 'CHYBA' -ForegroundColor Red
Write-Line (" $($_.Exception.Message)") Red
$out.Status = 'Error'
$out.Message = "Zápis registry selhal: $($_.Exception.Message)"
return $out
}
# Krok 2/3: Spustit servicing task a čekat na změnu stavu
Write-Host ' [2/3] Spouštím servicing task ' -NoNewline
$task = Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction SilentlyContinue
if (-not $task) {
Write-Host '— task nenalezen.' -ForegroundColor Yellow
Write-Line ' Registry nastavena; Windows zpracuje při servisním běhu.' DarkGray
Add-LogLine 'Krok 2: task nenalezen — přeskočeno'
} else {
$initAU = [int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates)
try {
Start-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction Stop
Start-Sleep -Seconds 2
$elapsed = 0
$state = 'Running'
$changed = $false
do {
Start-Sleep -Seconds 3
$elapsed += 3
Write-Host '.' -NoNewline -ForegroundColor DarkGray
$state = (Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction SilentlyContinue).State
$nowAU = [int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates)
if ($nowAU -ne $initAU) { $changed = $true; break }
} while ($state -eq 'Running' -and $elapsed -lt $TASK_TIMEOUT_SEC)
$finalAU = [int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates)
if ($changed) {
Write-Host (" OK ({0}s, AvailableUpdates -> 0x{1:X})" -f $elapsed, $finalAU) -ForegroundColor Green
} elseif ($elapsed -ge $TASK_TIMEOUT_SEC) {
Write-Host (" timeout {0}s (stav: {1})" -f $elapsed, $state) -ForegroundColor Yellow
} else {
Write-Host (" OK ({0}s, stav: {1})" -f $elapsed, $state) -ForegroundColor Green
}
Add-LogLine ("Krok 2: elapsed={0}s, stav={1}, AvailableUpdates=0x{2:X}" -f $elapsed, $state, $finalAU)
} catch {
Write-Host '— nepodařilo se spustit.' -ForegroundColor Yellow
Write-Line (" $($_.Exception.Message)") DarkGray
}
}
# Krok 3/3: Ověřit nový stav
Write-Host ' [3/3] Ověřuji nový stav ... ' -NoNewline
Start-Sleep -Seconds 2
$after = Invoke-Detection
Write-Host 'OK' -ForegroundColor Green
$out.Status = 'Applied'
$out.After = $after
$out.Message = 'Registry nastavena, task zpracován.'
Add-LogLine 'REMEDIACE KONEC: Applied'
return $out
}
#endregion
#region ── Stav napříč restarty ───────────────────────────────────────────────
function Save-ResumeState {
param($R, [int]$Cycle)
try {
if (-not (Test-Path $WORK_ROOT)) { New-Item -Path $WORK_ROOT -ItemType Directory -Force | Out-Null }
@{
ComputerName = $env:COMPUTERNAME
Timestamp = (Get-Date -Format 'yyyy-MM-dd HH:mm')
Cycle = $Cycle
Category = $R.Category
AvailableUpdates = ('0x{0:X}' -f [int]$R.Registry.AvailableUpdates)
} | ConvertTo-Json | Set-Content -LiteralPath $STATE_FILE -Encoding UTF8
} catch { }
}
function Get-ResumeState {
if (-not (Test-Path $STATE_FILE)) { return $null }
try { return (Get-Content -LiteralPath $STATE_FILE -Raw | ConvertFrom-Json) } catch { return $null }
}
function Clear-ResumeState {
if (Test-Path $STATE_FILE) { Remove-Item -LiteralPath $STATE_FILE -Force -ErrorAction SilentlyContinue }
}
#endregion
#region ── Main ───────────────────────────────────────────────────────────────
$isWhatIf = [bool]$WhatIfPreference
# Hlavička
Write-Host ''
Write-Rule 'Cyan'
Write-Host ' SECURE BOOT — KONTROLA A REMEDIACE CERTIFIKÁTŮ' -ForegroundColor White
Write-Host (" {0,-40} {1}" -f $env:COMPUTERNAME, (Get-Date -Format 'yyyy-MM-dd HH:mm')) -ForegroundColor DarkGray
Write-Rule 'Cyan'
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
Add-LogLine ('===== SPUŠTĚNO {0} | {1} | admin={2} | CheckOnly={3} AssumeYes={4} Force={5} AutoRestart={6} =====' -f `
$env:COMPUTERNAME, (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $isAdmin, `
$CheckOnly.IsPresent, $AssumeYes.IsPresent, $Force.IsPresent, $AutoRestart.IsPresent)
$prev = Get-ResumeState
if ($prev) {
Write-Host ''
Write-Host (" Navazuji na předchozí běh (cyklus #{0}, {1}, stav: {2})." -f `
$prev.Cycle, $prev.Timestamp, $prev.Category) -ForegroundColor DarkCyan
}
# Detekce stavu
Write-Host ''
Write-Host ' Zjišťuji stav serveru...' -ForegroundColor DarkGray
$result = Invoke-Detection
$prereqs = Get-Prerequisites -R $result -IsAdmin $isAdmin
# Zobrazení stavu (4 sekce: předpoklady, certifikáty, registry, události)
Show-Status -R $result -Prereqs $prereqs
# ── VMware: upozornění na manuální import KEK certifikátu ────────────────────
# Pokud bylo VM vytvořeno na ESXi starším než 9.x, nelze KEK nasadit automaticky.
if ($result.EnvironmentType -eq 'VMware VM' -and $result.SecureBoot.IsEnabled) {
Write-Host ''
Write-Host ' ┌─ UPOZORNĚNÍ — VMware VM ───────────────────────────────────────────────────┐' -ForegroundColor Yellow
Write-Host ' │ Pokud bylo toto VM vytvořeno na VMware ESXi starším než verze 9.x, │' -ForegroundColor Yellow
Write-Host ' │ certifikát KEK nelze nasadit automaticky a vyžaduje ruční import do │' -ForegroundColor Yellow
Write-Host ' │ UEFI firmware VM prostřednictvím správce ESXi hostitele. │' -ForegroundColor Yellow
Write-Host ' │ │' -ForegroundColor Yellow
Write-Host ' │ Postup: https://totalservice.atlassian.net/browse/KB-543 │' -ForegroundColor Cyan
Write-Host ' └──────────────────────────────────────────────────────────────────────────┘' -ForegroundColor Yellow
Add-LogLine 'Upozornění: VMware VM — na ESXi < 9.x je nutný ruční import KEK (KB-543)'
}
# ── CheckOnly: konec bez akce ─────────────────────────────────────────────────
if ($CheckOnly) {
Write-Host ''; Write-Rule
$exitCode = switch -Wildcard ($result.Category) { 'OK*' { 0 } 'UPDATE_*' { 1 } default { 2 } }
Write-Host (" CHECK: {0} (exit {1})" -f $result.CategoryLabel, $exitCode) -ForegroundColor $result.CategoryColor
Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray
Save-ResumeState -R $result -Cycle $(if ($prev) { [int]$prev.Cycle } else { 0 })
Flush-Log
exit $exitCode
}
# ── Ověřit hard prerekvizity ─────────────────────────────────────────────────
$hardFails = @($prereqs | Where-Object { $_.Hard -and -not $_.Ok })
if ($hardFails.Count) {
Write-Host ''
Write-Host ' Nelze pokračovat — vyřešte výše označené problémy a spusťte skript znovu.' -ForegroundColor Red
Flush-Log
exit 2
}
# ── Blokující kategorie ───────────────────────────────────────────────────────
$blockedMap = @{
'NO_SECUREBOOT' = 'Secure Boot není podporováno (Legacy BIOS). Dokumentujte server jako výjimku.'
'NO_SECUREBOOT_VM' = 'Secure Boot není podporováno (VM bez UEFI nebo vTPM). Dokumentujte jako výjimku.'
'SECUREBOOT_DISABLED' = 'Secure Boot je vypnutý. Zapnutí je mimo rozsah tohoto skriptu — nastavte v UEFI/BIOS.'
'SETUP_MODE' = 'Secure Boot je v Setup Mode. V UEFI/BIOS obnovte Secure Boot klíče (enroll PK) a spusťte skript znovu.'
'NOT_SUPPORTED' = 'Zařízení nepodporuje automatickou aktualizaci (ConfidenceLevel: Not Supported). Kontaktujte výrobce firmware.'
'BUILD_OUTDATED' = 'Tento build Windows neumí nasadit 2023 certifikáty. Nainstalujte Windows Update (min. 10/2025, KB5066835) a restartujte.'
}
if ($blockedMap.ContainsKey($result.Category)) {
Write-Host ''
Write-Host (" {0}" -f $blockedMap[$result.Category]) -ForegroundColor Yellow
Save-ResumeState -R $result -Cycle $(if ($prev) { [int]$prev.Cycle } else { 0 })
Flush-Log
exit 2
}
if ($result.Category -eq 'FIRMWARE_UPDATE_NEEDED') {
Write-Host ''
Write-Host (" Aktualizace pozastavena — firmware serveru {0} ji nepodporuje." -f $result.Hardware.Manufacturer) -ForegroundColor Yellow
Write-Host ' Aktualizujte firmware u výrobce a poté spusťte skript znovu.' -ForegroundColor Gray
Save-ResumeState -R $result -Cycle $(if ($prev) { [int]$prev.Cycle } else { 0 })
Flush-Log
exit 2
}
# ── UPDATE_FAILED — upozornit, ale přesto nabídnout opakování ────────────────
if ($result.Category -eq 'UPDATE_FAILED') {
Write-Host ''
$errEvtId = $result.Registry.UEFICA2023ErrorEvent
if ($errEvtId -and [int]$errEvtId -ne 0) {
Write-Host (" Předchozí pokus selhal (UEFICA2023ErrorEvent={0}). Zkuste znovu nebo aktualizujte firmware." -f $errEvtId) -ForegroundColor Red
} elseif ($result.EventLog.ById[1795]) {
Write-Host (" Selhání firmwaru (EventID 1795, {0}). Aktualizujte firmware nebo Hyper-V hostitele (KB5085790)." -f $result.EventLog.ById[1795].Time) -ForegroundColor Red
} else {
Write-Host ' Předchozí pokus selhal. Zkuste spustit remediaci znovu.' -ForegroundColor Red
}
}
$cycle = if ($prev) { [int]$prev.Cycle } else { 0 }
# ── Předběžná kontrola: povinné 2023 certy přítomny? ─────────────────────────
# Pokud ano a není -Force → ukončit bez remediace.
$certsReady = $result.Certificates.KEK.Has2023 -and $result.Certificates.DB.Has2023WindowsUEFI
if ($certsReady -and ($result.Category -like 'OK*') -and -not $Force) {
Write-Host ''
Write-Host (" {0}" -f $result.CategoryLabel) -ForegroundColor $result.CategoryColor
Write-Host ' Na tomto serveru není nutná žádná remediace.' -ForegroundColor Green
Write-Host ' (Tip: parametr -Force vynutí remediaci i v tomto stavu.)' -ForegroundColor DarkGray
Write-Host ''; Write-Rule 'Cyan'
Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray
Write-Rule 'Cyan'; Write-Host ''
if ($result.Category -like 'OK*') { Clear-ResumeState } else { Save-ResumeState -R $result -Cycle $cycle }
Flush-Log
exit 0
}
if ($certsReady -and $Force) {
Write-Host ''
Write-Host ' ! Certifikáty jsou přítomny, ale -Force umožňuje spustit remediaci znovu.' -ForegroundColor Yellow
}
# ── UPDATE_BOOTMANAGER: certy OK, ale bootloader nepoužívá 2023 CA ───────────
if ($result.Category -eq 'UPDATE_BOOTMANAGER') {
Write-Host ''
Write-Host ' Certifikáty jsou v UEFI databázích, ale Boot Manager dosud nepoužívá 2023 CA.' -ForegroundColor Yellow
Write-Host ' Remediace spustí task, který Boot Manager 2023 připraví — poté bude nutný restart.' -ForegroundColor Gray
}
# ── AU=0x4100: Boot Manager staged, stačí RESTART ────────────────────────────
$auNow = [int]$result.Registry.AvailableUpdates
if ($auNow -eq 0x4100) {
Show-TwoRestartBlock -R $result
if (-not $isWhatIf) {
Invoke-RestartOffer -Auto:$AutoRestart.IsPresent
}
Write-Host ''; Write-Rule 'Cyan'
Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray
Write-Rule 'Cyan'; Write-Host ''
Save-ResumeState -R $result -Cycle $cycle
Flush-Log
exit 1
}
# ── Zkontrolovat administrátorská práva před remediací ────────────────────────
if (-not $isAdmin) {
Write-Host ''
Write-Host ' Remediaci nelze provést bez práv Administrator.' -ForegroundColor Yellow
Write-Host ' Spusťte PowerShell "Run as administrator" a zkuste znovu.' -ForegroundColor Gray
Flush-Log
exit 2
}
# ── WhatIf: zobrazit co by se stalo ─────────────────────────────────────────
$remediation = $null
if ($isWhatIf) {
Write-Host ''
Write-Host ' -WhatIf — co by remediace udělala (bez změn):' -ForegroundColor Cyan
Write-Host ' - Nastaví MicrosoftUpdateManagedOptIn=1, HighConfidenceOptOut=0' -ForegroundColor Gray
$auWhatIfLabel = if ($auNow -eq 0) { '0x5944 (první inicializace)' } else { '0x{0:X} (zachováno)' -f $auNow }
Write-Host (" - AvailableUpdates: {0}" -f $auWhatIfLabel) -ForegroundColor Gray
Write-Host ' - Spustí servicing task a bude čekat na změnu stavu' -ForegroundColor Gray
$autoRestartNote = if ($AutoRestart) { 'ANO (-AutoRestart)' } else { 'NE (nabídne dotaz)' }
Write-Host (" - Restart serveru: {0}" -f $autoRestartNote) -ForegroundColor Gray
} else {
# ── Upozornění na BitLocker před souhlasem s remediací ───────────────────
# Zobrazuje se vždy, když je BitLocker aktivní — bez ohledu na typ protektoru.
$blPre = $result.BitLocker
$blPreActv = $blPre -and ($blPre.Status -eq 'On' -or $blPre.Status -eq '1' -or $blPre.Status -eq '2')
if ($blPreActv) {
Write-Host ''
if ($blPre.UsesPcr) {
Write-Host ' ┌─ DOPORUČENÍ PŘED REMEDIACÍ — BitLocker ───────────────────────────────────┐' -ForegroundColor Yellow
Write-Host ' │ Systémový svazek je chráněn nástrojem BitLocker s vazbou na TPM/PCR7. │' -ForegroundColor Yellow
Write-Host ' │ Aktualizace Boot Manageru může při příštím restartu vyvolat recovery │' -ForegroundColor Yellow
Write-Host ' │ prompt a znemožnit automatické spuštění serveru. │' -ForegroundColor Yellow
Write-Host ' │ │' -ForegroundColor Yellow
Write-Host ' │ Před zahájením remediace ověřte dostupnost recovery klíče: │' -ForegroundColor Yellow
Write-Host ' │ Active Directory (AD DS), Azure AD nebo bezpečný trezor klíčů. │' -ForegroundColor Yellow
Write-Host ' └──────────────────────────────────────────────────────────────────────────┘' -ForegroundColor Yellow
} else {
Write-Host ' ┌─ DOPORUČENÍ PŘED REMEDIACÍ — BitLocker ───────────────────────────────────┐' -ForegroundColor Yellow
Write-Host ' │ Systémový svazek je chráněn nástrojem BitLocker. │' -ForegroundColor Yellow
Write-Host ' │ Před zahájením remediace ověřte dostupnost recovery klíče (AD, trezor). │' -ForegroundColor Yellow
Write-Host ' └──────────────────────────────────────────────────────────────────────────┘' -ForegroundColor Yellow
}
Add-LogLine ("Upozornění: BitLocker aktivní před remediací (Protectors: {0}, PCR: {1})" -f $blPre.Protectors, $blPre.UsesPcr)
}
# ── Nabídnout remediaci ──────────────────────────────────────────────────
$question = switch ($result.Category) {
'UPDATE_NEEDED' { 'Server potřebuje aktualizaci Secure Boot certifikátů. Chcete zahájit proces?' }
'UPDATE_PARTIAL' { 'Aktualizace probíhá — část certifikátů je nasazena. Spustit task pro posun procesu?' }
'UPDATE_PENDING_RESTART' { 'Certifikáty jsou v procesu. Spustit servicing task?' }
'UPDATE_FAILED' { 'Předchozí pokus selhal. Chcete zkusit remediaci znovu?' }
default { 'Chcete spustit remediaci Secure Boot certifikátů?' }
}
$detail = 'Nastaví registry (KB5068202) a spustí servicing task. Server NEBUDE restartován (bez -AutoRestart).'
if (Get-UserConsent -Question $question -Detail $detail) {
$cycle++
Add-LogLine ("Cyklus #{0} | {1} | kategorie={2}" -f $cycle, $result.Hostname, $result.Category)
$remediation = Invoke-Remediation
Flush-Log
} else {
Write-Host ''
Write-Host ' Remediace neprovedena — spusťte skript znovu, až budete připraveni.' -ForegroundColor Yellow
}
}
# ── Závěr ─────────────────────────────────────────────────────────────────────
Write-Host ''; Write-Rule 'Cyan'
if ($remediation -and $remediation.Status -eq 'Applied') {
$after = $remediation.After
$afterAU = [int]$after.Registry.AvailableUpdates
$afterDone = Test-RemediationComplete -R $after
Write-Host ' Stav po remediaci:' -ForegroundColor Cyan
Show-Certificates -Cert $after.Certificates
Show-Registry -Reg $after.Registry
Write-Host ''
if ($afterDone) {
Write-Host (" [{0}] HOTOVO — Secure Boot certifikáty 2023 úspěšně nasazeny!" -f $SYM_DONE) -ForegroundColor Green
Write-Host ' Podmínky splněny: certifikáty v DB/KEK, UEFICA2023Status=Updated, AvailableUpdates=0x0/0x4000.' -ForegroundColor Green
Clear-ResumeState
} elseif ($afterAU -eq 0x4100) {
Show-TwoRestartBlock -R $after
Invoke-RestartOffer -Auto:$AutoRestart.IsPresent
Save-ResumeState -R $after -Cycle $cycle
} else {
Write-Host ' Remediace proběhla — proces vyžaduje restart pro dokončení.' -ForegroundColor Yellow
Write-Host ''
Write-Host ' Další kroky:' -ForegroundColor White
Write-Host ' 1. Restartujte server.' -ForegroundColor Gray
Write-Host ' 2. Spusťte skript znovu — opakujte, dokud nebude HOTOVO.' -ForegroundColor Gray
Write-Host (" 3. Cíl: AvailableUpdates=0x0, UEFICA2023Status=Updated, certifikáty v KEK+DB.") -ForegroundColor DarkGray
Save-ResumeState -R $after -Cycle $cycle
}
} elseif ($remediation -and $remediation.Status -eq 'Error') {
Write-Host (" CHYBA REMEDIACE — {0}" -f $remediation.Message) -ForegroundColor Red
Save-ResumeState -R $result -Cycle $cycle
} else {
# Bez remediace (odmítnutí / WhatIf / blocked)
Write-Host ' KONEC — bez změn na serveru.' -ForegroundColor Cyan
$finalR = $result
if ($finalR.Category -like 'OK*') { Clear-ResumeState } else { Save-ResumeState -R $finalR -Cycle $cycle }
}
Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray
Write-Rule 'Cyan'; Write-Host ''
Flush-Log
#endregion