Add Invoke-SecureBootRemediation.ps1

This commit is contained in:
Petr Stepan
2026-06-06 16:27:51 +02:00
parent 2aaf44ad90
commit 6229f9b2ee
+305 -123
View File
@@ -15,12 +15,12 @@
- ConfidenceLevel (REG_SZ): High Confidence / Temporarily Paused / Not Supported / ... - ConfidenceLevel (REG_SZ): High Confidence / Temporarily Paused / Not Supported / ...
- AvailableUpdates: 0x5944 -> 0x5904 -> 0x5104 -> 0x4104 -> 0x4100 -> 0x4000 -> 0x0 - AvailableUpdates: 0x5944 -> 0x5904 -> 0x5104 -> 0x4104 -> 0x4100 -> 0x4000 -> 0x0
- Cílový stav: AvailableUpdates=0x0, UEFICA2023Status=Updated, WindowsUEFICA2023Capable=2 - Cílový stav: AvailableUpdates=0x0, UEFICA2023Status=Updated, WindowsUEFICA2023Capable=2
- Events (TPM-WMI): 1808=hotovo, 1799=boot mgr 2023, 1795=chyba firmwaru, - Events (TPM-WMI): 1795=chyba firmwaru, 1796=selhání zápisu UEFI var,
1802=pozastaveno, 1803=odloženo (monitor), 1801=zatím neaplikováno (monitor) 1802=pozastaveno (firmware), 1803=odloženo (monitor), 1801=čeká (monitor)
Skript ZÁMĚRNĚ NERESTARTUJE server. Sleduje stav i napříč restarty (state.json) a umí Skript nabídne restart (interaktivní dotaz) jen při AvailableUpdates=0x4100 (Boot Manager
volitelně po dalším restartu spustit kontrolu automaticky (-RegisterResume) — stále BEZ staged). Ve všech ostatních stavech task spustí a posune hodnotu. Sleduje stav napříč
auto-restartu. restarty (state.json). Volitelně registruje RunOnce pro auto-check po restartu (-RegisterResume).
.PARAMETER CheckOnly .PARAMETER CheckOnly
Jen detekce + checklist + exit kód (0=hotovo, 1=nutná akce, 2=blokováno). Bez změn/dotazu. Jen detekce + checklist + exit kód (0=hotovo, 1=nutná akce, 2=blokováno). Bez změn/dotazu.
@@ -35,8 +35,8 @@
Nastaví registry, ale nespustí servicing task (spustí se sám cca á 12 h). Nastaví registry, ale nespustí servicing task (spustí se sám cca á 12 h).
.PARAMETER SkipBootManagerFileCheck .PARAMETER SkipBootManagerFileCheck
Neověřuje bootmgfw.efi na ESP přes mount+certutil. Dokončení se i tak pozná z Neověřuje bootmgfw.efi na ESP přes mount+certutil. Dokončení se pozná z
WindowsUEFICA2023Capable=2 / Event 1808 / 1799. WindowsUEFICA2023Capable=2 a UEFICA2023Status=Updated.
.PARAMETER RegisterResume .PARAMETER RegisterResume
Po remediaci, kde zbývá restart, zaregistruje RunOnce, který po PŘÍŠTÍM (ručním) restartu Po remediaci, kde zbývá restart, zaregistruje RunOnce, který po PŘÍŠTÍM (ručním) restartu
@@ -123,7 +123,6 @@ $SYM_PENDING = ' '
#region ── Log / barevný výstup ─────────────────────────────────────────────── #region ── Log / barevný výstup ───────────────────────────────────────────────
$scriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { (Get-Location).Path }
# Jeden společný log na LOKÁLNÍM disku serveru (%ProgramData%), připojovaný pod sebe přes běhy. # Jeden společný log na LOKÁLNÍM disku serveru (%ProgramData%), připojovaný pod sebe přes běhy.
# Záměrně NE vedle skriptu — ten může být RDP-redirected disk, kde per-řádkový zápis vytváří # Záměrně NE vedle skriptu — ten může být RDP-redirected disk, kde per-řádkový zápis vytváří
# poškozené (null) soubory. Řádky se bufferují a zapíšou jednorázově (Flush-Log), což je odolné. # poškozené (null) soubory. Řádky se bufferují a zapíšou jednorázově (Flush-Log), což je odolné.
@@ -243,25 +242,44 @@ function Get-BitLockerInfo {
#region ── Detekce — certifikáty / registry / events / boot manager ─────────── #region ── Detekce — certifikáty / registry / events / boot manager ───────────
function Parse-EFISignatureList { 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, v každé seznam SignatureEntry s 16B GUID prefixem.
param([byte[]]$Bytes) param([byte[]]$Bytes)
$certs=@(); if (-not $Bytes -or $Bytes.Length -lt 28) { return $certs } $certs = @()
$G=[byte[]](0xa1,0x59,0xc0,0xa5,0xe4,0x94,0xa7,0x4a,0x87,0xb5,0xab,0x15,0x5c,0x2b,0xf0,0x72) if (-not $Bytes -or $Bytes.Length -lt 28) { return $certs }
$o=0
while ($o + 28 -le $Bytes.Length) { # GUID pro EFI_CERT_X509_GUID: {a5c059a1-94e4-4aa7-87b5-ab155c2bf072} (little-endian bytes)
$gid=$Bytes[$o..($o+15)]; $listSize=[BitConverter]::ToUInt32($Bytes,$o+16); $hdr=[BitConverter]::ToUInt32($Bytes,$o+20); $sz=[BitConverter]::ToUInt32($Bytes,$o+24) $x509Guid = [byte[]](0xa1,0x59,0xc0,0xa5,0xe4,0x94,0xa7,0x4a,0x87,0xb5,0xab,0x15,0x5c,0x2b,0xf0,0x72)
if ($listSize -lt 28 -or $listSize -gt ($Bytes.Length-$o)) { break }
$x=$true; for ($i=0;$i -lt 16;$i++){ if ($gid[$i] -ne $G[$i]){ $x=$false; break } } $pos = 0
if ($x -and $sz -gt 16) { while ($pos + 28 -le $Bytes.Length) {
$so=$o+28+$hdr; $end=$o+$listSize $sigTypeGuid = $Bytes[$pos..($pos+15)]
while ($so+$sz -le $end) { $listSize = [BitConverter]::ToUInt32($Bytes, $pos+16)
$co=$so+16; $cs=[int]$sz-16 $headerSize = [BitConverter]::ToUInt32($Bytes, $pos+20)
if ($co+$cs -le $Bytes.Length -and $cs -gt 0) { $sigSize = [BitConverter]::ToUInt32($Bytes, $pos+24)
try { $certs += New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(, [byte[]]($Bytes[$co..($co+$cs-1)])) } catch { }
if ($listSize -lt 28 -or $listSize -gt ($Bytes.Length - $pos)) { break }
# Zpracovat pouze X.509 záznamy
$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 { }
} }
$so += $sz $entryPos += $sigSize
} }
} }
$o += $listSize $pos += $listSize
} }
return $certs return $certs
} }
@@ -281,20 +299,25 @@ function Get-CertificateStatus {
DbxRevokesPCA2011=$false; AnyExpiring2011=$false DbxRevokesPCA2011=$false; AnyExpiring2011=$false
} }
try { try {
foreach ($c in (Parse-EFISignatureList -Bytes (Get-SecureBootUEFI -Name KEK -ErrorAction Stop).Bytes)) { $kekBytes = (Get-SecureBootUEFI -Name KEK -ErrorAction Stop).Bytes
$s=$c.Subject; $info=Convert-CertToInfo $c foreach ($cert in (Parse-EFISignatureList -Bytes $kekBytes)) {
if ($s -like '*KEK CA 2011*') { $st.KEK.Has2011=$true; $st.KEK.Certs2011+=$info } $subj = $cert.Subject
if ($s -like "*$CN_KEK2023*" -or $s -like '*KEK*CA 2023*') { $st.KEK.Has2023=$true; $st.KEK.Certs2023+=$info } $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 } } catch { $st.KEK.Error=$_.Exception.Message }
try { try {
foreach ($c in (Parse-EFISignatureList -Bytes (Get-SecureBootUEFI -Name db -ErrorAction Stop).Bytes)) { $dbBytes = (Get-SecureBootUEFI -Name db -ErrorAction Stop).Bytes
$s=$c.Subject; $info=Convert-CertToInfo $c foreach ($cert in (Parse-EFISignatureList -Bytes $dbBytes)) {
if ($s -like '*UEFI CA 2011*') { $st.DB.Has2011UEFI=$true; $st.DB.Certs2011+=$info } $subj = $cert.Subject
if ($s -like '*Windows Production PCA 2011*' -or $s -like '*Windows PCA 2011*') { $st.DB.Has2011WindowsPCA=$true; $st.DB.Certs2011+=$info } $info = Convert-CertToInfo $cert
if ($s -like "*$CN_UEFI2023*" -and $s -notlike '*Option ROM*' -and $s -notlike '*Windows UEFI*') { $st.DB.Has2023UEFI=$true; $st.DB.Certs2023+=$info } if ($subj -like '*UEFI CA 2011*') { $st.DB.Has2011UEFI=$true; $st.DB.Certs2011+=$info }
if ($s -like "*$CN_OPTROM2023*") { $st.DB.Has2023OptionROM=$true; $st.DB.Certs2023+=$info } if ($subj -like '*Windows Production PCA 2011*' -or $subj -like '*Windows PCA 2011*') { $st.DB.Has2011WindowsPCA=$true; $st.DB.Certs2011+=$info }
if ($s -like "*$CN_WINUEFI2023*") { $st.DB.Has2023WindowsUEFI=$true; $st.DB.Certs2023+=$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 } } catch { $st.DB.Error=$_.Exception.Message }
# ASCII fallback (kdyby X.509 parse selhal) — jen doplní booleany # ASCII fallback (kdyby X.509 parse selhal) — jen doplní booleany
@@ -348,8 +371,8 @@ function Get-AvailableUpdatesText {
if ($null -eq $v) { return '(nenastaveno)' } if ($null -eq $v) { return '(nenastaveno)' }
$iv=[int]$v; $hex='0x{0:X}' -f $iv $iv=[int]$v; $hex='0x{0:X}' -f $iv
$n = switch ($iv) { $n = switch ($iv) {
0 {'vše hotovo (proces dokončen)'} 0x4000 {'vše aplikováno — čeká na finální restart'} 0 {'vše hotovo (proces dokončen)'} 0x4000 {'vše aplikováno — task čistí guard bit'}
0x4100 {'boot manager 2023 nasazen na ESP'} 0x4104 {'Microsoft UEFI CA 2023 v DB'} 0x4100 {'boot manager 2023 nasazen na ESP — čeká na RESTART'} 0x4104 {'Microsoft UEFI CA 2023 v DB'}
0x5104 {'Option ROM UEFI CA 2023 v DB'} 0x5904 {'Windows 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 {'zbývá aplikovat'} } 0x5944 {'naplánována plná sada (start)'} default {'zbývá aplikovat'} }
return "$hex ($n)" return "$hex ($n)"
@@ -362,9 +385,22 @@ function Get-EventLogStatus {
$events = Get-WinEvent -FilterHashtable @{ LogName='System'; Id=$ids } -MaxEvents 40 -ErrorAction Stop $events = Get-WinEvent -FilterHashtable @{ LogName='System'; Id=$ids } -MaxEvents 40 -ErrorAction Stop
if ($events) { if ($events) {
$sorted = $events | Sort-Object TimeCreated -Descending $sorted = $events | Sort-Object TimeCreated -Descending
foreach ($id in $ids) { $x=$sorted | Where-Object {$_.Id -eq $id} | Select-Object -First 1; if ($x){ $e.ById[$id]=@{Time=$x.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')} } } # Index posledního výskytu každého ID
foreach ($x in ($sorted|Select-Object -First 8)) { $e.RelevantEvents += [ordered]@{ EventId=$x.Id; TimeCreated=$x.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss'); Level=$x.LevelDisplayName } } foreach ($id in $ids) {
$last=$sorted|Select-Object -First 1; $e.LastEventId=$last.Id; $e.LastEventTime=$last.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') $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') } }
}
# Posledních 8 událostí pro -Detailed výpis
foreach ($ev in ($sorted | Select-Object -First 8)) {
$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 { } catch {
if ($_.CategoryInfo.Reason -ne 'NoMatchingEventsException') { $e.Error=$_.Exception.Message } if ($_.CategoryInfo.Reason -ne 'NoMatchingEventsException') { $e.Error=$_.Exception.Message }
@@ -412,8 +448,15 @@ function Build-Phases {
param($Sb, $Cert, $Boot, $TaskExists, $Reg, $Evt) param($Sb, $Cert, $Boot, $TaskExists, $Reg, $Evt)
$e1795=$Evt.ById[1795]; $e1796=$Evt.ById[1796] $e1795=$Evt.ById[1795]; $e1796=$Evt.ById[1796]
$cap=$Reg.WindowsUEFICA2023Capable $cap=$Reg.WindowsUEFICA2023Capable
$bmActive = ($cap -eq 2) -or ($null -ne $Evt.ById[1808]) -or ($null -ne $Evt.ById[1799]) # Pouze Capable=2 je autoritativní signál aktivního Boot Manageru 2023.
# Eventy 1808/1799 nejsou Done signálem — mohou být ze starých/předchozích běhů a jsou nespolehlivé.
$bmActive = ($cap -eq 2)
$bmStaged = [bool]$Boot.EspHas2023 $bmStaged = [bool]$Boot.EspHas2023
# UEFICA2023Status musí být "Updated" pro potvrzení, že cert servisování proběhlo.
# Pokud klíč zcela chybí (starý build / BeforeServicing), netlumíme — BUILD_OUTDATED to zachytí jinak.
$statusUpdated = ($Reg.UEFICA2023StatusText -eq 'Updated')
$statusAbsent = ($null -eq $Reg.UEFICA2023Status)
$certServicingDone = $statusUpdated -or $statusAbsent
$ph=[ordered]@{} $ph=[ordered]@{}
function _p($req,$done,$label){ [ordered]@{ Req=$req; Done=[bool]$done; Label=$label; State=$null; Note=$null } } function _p($req,$done,$label){ [ordered]@{ Req=$req; Done=[bool]$done; Label=$label; State=$null; Note=$null } }
@@ -421,6 +464,7 @@ function Build-Phases {
$ph['TaskExists'] = _p $true $TaskExists 'Servicing task k dispozici' $ph['TaskExists'] = _p $true $TaskExists 'Servicing task k dispozici'
$ph['Kek2023'] = _p $true $Cert.KEK.Has2023 "KEK: Microsoft Corporation $CN_KEK2023" $ph['Kek2023'] = _p $true $Cert.KEK.Has2023 "KEK: Microsoft Corporation $CN_KEK2023"
$ph['Db2023Windows'] = _p $true $Cert.DB.Has2023WindowsUEFI "DB: $CN_WINUEFI2023" $ph['Db2023Windows'] = _p $true $Cert.DB.Has2023WindowsUEFI "DB: $CN_WINUEFI2023"
$ph['CertServicingStatus'] = _p $true $certServicingDone 'Certifikáty aktualizovány (UEFICA2023Status=Updated)'
$ph['BootManager2023'] = _p $true $bmActive 'Boot Manager aktivní (bootuje se z Windows UEFI CA 2023)' $ph['BootManager2023'] = _p $true $bmActive 'Boot Manager aktivní (bootuje se z Windows UEFI CA 2023)'
$ph['Db2023ThirdParty'] = _p $false $Cert.DB.Has2023UEFI "DB: $CN_UEFI2023 (3rd-party, volitelné)" $ph['Db2023ThirdParty'] = _p $false $Cert.DB.Has2023UEFI "DB: $CN_UEFI2023 (3rd-party, volitelné)"
$ph['Db2023OptionRom'] = _p $false $Cert.DB.Has2023OptionROM "DB: $CN_OPTROM2023 (volitelné)" $ph['Db2023OptionRom'] = _p $false $Cert.DB.Has2023OptionROM "DB: $CN_OPTROM2023 (volitelné)"
@@ -438,10 +482,26 @@ function Build-Phases {
elseif ($e1795) { $p.State='Fail'; $p.Note='chyba firmwaru (Event 1795)' } elseif ($e1795) { $p.State='Fail'; $p.Note='chyba firmwaru (Event 1795)' }
else { $p.State='Pending'; $p.Note='zbývá nasadit' } else { $p.State='Pending'; $p.Note='zbývá nasadit' }
} }
'CertServicingStatus' {
$p.State = 'Pending'
if ($Reg.UEFICA2023StatusText -eq 'InProgress') {
$p.Note = 'servisování probíhá — task ještě nedoběhl nebo čeká na restart'
} elseif ($Reg.UEFICA2023StatusText -eq 'NotStarted') {
$p.Note = 'task zatím nezahájil servisování certifikátů'
} elseif ($Reg.UEFICA2023StatusText -eq 'KeyNotPresent') {
$p.Note = 'hodnota UEFICA2023Status není nastavena (starý build nebo před první remediací)'
} else {
$p.Note = "UEFICA2023Status: $($Reg.UEFICA2023StatusText)"
}
}
'BootManager2023' { 'BootManager2023' {
if ($bmStaged) { $p.State='Pending'; $p.Note="$arrow nasazen na ESP — vyžaduje RESTART" } if ($bmStaged -or ($null -ne $cap -and [int]$cap -ge 1)) {
elseif ($kekDb) { $p.State='Pending'; $p.Note="$arrow zbývá — vyžaduje RESTART" } $p.State='Pending'; $p.Note="$arrow cert v DB (Capable=$cap) — aktivuje se RESTARTEM"
else { $p.State='Pending'; $p.Note='až po nasazení KEK + DB' } } elseif ($kekDb) {
$p.State='Pending'; $p.Note="$arrow zbývá — vyžaduje RESTART"
} else {
$p.State='Pending'; $p.Note='až po nasazení KEK + DB'
}
} }
default { default {
if ($e1795) { $p.State='Fail'; $p.Note='chyba firmwaru (Event 1795)' } else { $p.State='Pending'; $p.Note='zbývá nasadit' } if ($e1795) { $p.State='Fail'; $p.Note='chyba firmwaru (Event 1795)' } else { $p.State='Pending'; $p.Note='zbývá nasadit' }
@@ -456,14 +516,23 @@ function Get-RemediationCategory {
$sb=$Result.SecureBoot; $reg=$Result.Registry; $evt=$Result.EventLog $sb=$Result.SecureBoot; $reg=$Result.Registry; $evt=$Result.EventLog
$env=$Result.EnvironmentType; $ph=$Result.Phases; $cert=$Result.Certificates; $mode=$Result.OperatingMode $env=$Result.EnvironmentType; $ph=$Result.Phases; $cert=$Result.Certificates; $mode=$Result.OperatingMode
# ── Základní blokátory ────────────────────────────────────────────────────
if (-not $sb.IsUEFI -or -not $sb.IsSupported) { if (-not $sb.IsUEFI -or -not $sb.IsSupported) {
if ($env -like '*VM*') { return @{ Code='NO_SECUREBOOT_VM'; Tag='[X]'; Color='DarkGray'; Label='Secure Boot nepodporováno (VM bez vTPM/UEFI)' } } $lbl = if ($env -like '*VM*') { 'Secure Boot nepodporováno (VM bez vTPM/UEFI)' } else { 'Secure Boot nepodporováno (Legacy BIOS)' }
return @{ Code='NO_SECUREBOOT'; Tag='[X]'; Color='DarkGray'; Label='Secure Boot nepodporováno (Legacy BIOS)' } $cod = if ($env -like '*VM*') { 'NO_SECUREBOOT_VM' } else { 'NO_SECUREBOOT' }
return @{ Code=$cod; Tag='[X]'; Color='DarkGray'; Label=$lbl }
}
if (-not $sb.IsEnabled) {
return @{ Code='SECUREBOOT_DISABLED'; Tag='[OFF]'; Color='Yellow'; Label='Secure Boot vypnuto' }
}
if ($mode -eq 'Setup') {
return @{ Code='SETUP_MODE'; Tag='[!]'; Color='Magenta'; Label='Secure Boot v Setup Mode — aktualizaci nelze dokončit (chybí enrolled PK)' }
}
if (-not $ph['TaskExists'].Done) {
return @{ Code='TASK_MISSING'; Tag='[!]'; Color='Magenta'; Label='Chybí servicing task — nainstalujte aktuální kumulativní update (min. build 10/2025)' }
} }
if (-not $sb.IsEnabled) { return @{ Code='SECUREBOOT_DISABLED'; Tag='[OFF]'; Color='Yellow'; Label='Secure Boot vypnuto' } }
if ($mode -eq 'Setup') { return @{ Code='SETUP_MODE'; Tag='[!]'; Color='Magenta'; Label='Secure Boot v Setup Mode — aktualizaci nelze dokončit (chybí enrolled PK)' } }
if (-not $ph['TaskExists'].Done) { return @{ Code='TASK_MISSING'; Tag='[!]'; Color='Magenta'; Label='Chybí servicing task — nainstalujte aktuální kumulativní update (min. build 10/2025)' } }
# ── Chyby a blokování ─────────────────────────────────────────────────────
$errEvt = $reg.UEFICA2023ErrorEvent -and ([int]$reg.UEFICA2023ErrorEvent -ne 0) $errEvt = $reg.UEFICA2023ErrorEvent -and ([int]$reg.UEFICA2023ErrorEvent -ne 0)
if ($evt.ById[1795] -or $errEvt -or $reg.UEFICA2023StatusText -eq 'Failed') { if ($evt.ById[1795] -or $errEvt -or $reg.UEFICA2023StatusText -eq 'Failed') {
return @{ Code='UPDATE_FAILED'; Tag='[FAIL]'; Color='Red'; Label='Selhání aktualizace (chyba firmwaru / UEFICA2023ErrorEvent)' } return @{ Code='UPDATE_FAILED'; Tag='[FAIL]'; Color='Red'; Label='Selhání aktualizace (chyba firmwaru / UEFICA2023ErrorEvent)' }
@@ -475,28 +544,38 @@ function Get-RemediationCategory {
return @{ Code='FIRMWARE_UPDATE_NEEDED'; Tag='[FW]'; Color='Magenta'; Label='Aktualizace pozastavena (známý problém) — zkontrolujte firmware u OEM' } return @{ Code='FIRMWARE_UPDATE_NEEDED'; Tag='[FW]'; Color='Magenta'; Label='Aktualizace pozastavena (známý problém) — zkontrolujte firmware u OEM' }
} }
# Build příliš starý: DB/KEK už mají 2023, ale boot-manager servicing hodnoty vůbec neexistují # ── Starý build: certy jsou v KEK/DB, ale chybí servicing infrastruktura pro Boot Manager ──
# (WindowsUEFICA2023Capable i UEFICA2023Status absentní) a Boot Manager není aktivní → OS nemá # WindowsUEFICA2023Capable i UEFICA2023Status zcela chybí → Windows Update nutný.
# 2023-podepsaný boot manager ani jeho servicing. Restarty nepomohou, pomůže jen Windows Update.
$certApplied = $ph['Kek2023'].Done -or $ph['Db2023Windows'].Done $certApplied = $ph['Kek2023'].Done -or $ph['Db2023Windows'].Done
$auNow = [int]$reg.AvailableUpdates $auNow = [int]$reg.AvailableUpdates
$bmStaged = ([bool]$Result.BootManager.EspHas2023) -or ($auNow -eq 0x4100) -or ($auNow -eq 0x4000) $bmStaged = ([bool]$Result.BootManager.EspHas2023) -or ($auNow -eq 0x4100) -or ($auNow -eq 0x4000)
if ($certApplied -and -not $ph['BootManager2023'].Done -and -not $bmStaged -and ($null -eq $reg.WindowsUEFICA2023Capable) -and ($null -eq $reg.UEFICA2023Status)) { if ($certApplied -and -not $ph['BootManager2023'].Done -and -not $bmStaged `
-and ($null -eq $reg.WindowsUEFICA2023Capable) -and ($null -eq $reg.UEFICA2023Status)) {
return @{ Code='BUILD_OUTDATED'; Tag='[!]'; Color='Magenta'; Label='Build je příliš starý — chybí servicing pro Boot Manager 2023. Nutný Windows Update.' } return @{ Code='BUILD_OUTDATED'; Tag='[!]'; Color='Magenta'; Label='Build je příliš starý — chybí servicing pro Boot Manager 2023. Nutný Windows Update.' }
} }
$req=@('Kek2023','Db2023Windows','BootManager2023') # ── Fázové vyhodnocení ────────────────────────────────────────────────────
$missing=@($req|Where-Object{ -not $ph[$_].Done }); $missingLabels=@($missing|ForEach-Object{ $ph[$_].Label }) $req = @('Kek2023','Db2023Windows','CertServicingStatus','BootManager2023')
$anyApplied = $ph['Kek2023'].Done -or $ph['Db2023Windows'].Done -or $ph['BootManager2023'].Done -or $cert.DB.Has2023UEFI -or $cert.DB.Has2023OptionROM -or [bool]$Result.BootManager.EspHas2023 $missing = @($req | Where-Object { -not $ph[$_].Done })
$missingLabels = @($missing | ForEach-Object { $ph[$_].Label })
$anyApplied = $ph['Kek2023'].Done -or $ph['Db2023Windows'].Done -or $ph['BootManager2023'].Done `
-or $cert.DB.Has2023UEFI -or $cert.DB.Has2023OptionROM -or [bool]$Result.BootManager.EspHas2023
if ($missing.Count -eq 0) { if ($missing.Count -eq 0) {
if ($cert.AnyExpiring2011) { return @{ Code='OK_TRANSITION'; Tag="[$SYM_DONE]"; Color='Green'; Label='HOTOVO — 2023 certifikáty i aktivní Boot Manager (staré 2011 ještě přítomné, což je normální)' } } if ($cert.AnyExpiring2011) {
return @{ Code='OK_TRANSITION'; Tag="[$SYM_DONE]"; Color='Green'
Label='HOTOVO — 2023 certifikáty i aktivní Boot Manager (staré 2011 ještě přítomné, což je normální)' }
}
return @{ Code='OK'; Tag="[$SYM_DONE]"; Color='Green'; Label='HOTOVO — kompletní 2023 sada, aktivní Boot Manager, 2011 odstraněny' } return @{ Code='OK'; Tag="[$SYM_DONE]"; Color='Green'; Label='HOTOVO — kompletní 2023 sada, aktivní Boot Manager, 2011 odstraněny' }
} }
if ($missing.Count -eq 1 -and $missing[0] -eq 'BootManager2023') { if ($missing.Count -eq 1 -and $missing[0] -eq 'BootManager2023') {
return @{ Code='UPDATE_PENDING'; Tag='[~]'; Color='Yellow'; Label='KEK i DB hotové — zbývá aktivovat Boot Manager: vyžaduje RESTART' } return @{ Code='UPDATE_PENDING'; Tag='[~]'; Color='Yellow'
Label='UEFICA2023Status=Updated, KEK i DB hotové — zbývá aktivovat Boot Manager: vyžaduje RESTART' }
}
if ($anyApplied) {
return @{ Code='UPDATE_PARTIAL'; Tag='[~]'; Color='Cyan'
Label='Probíhá po částech — zbývá: ' + ($missingLabels -join '; ') }
} }
if ($anyApplied) { return @{ Code='UPDATE_PARTIAL'; Tag='[~]'; Color='Cyan'; Label=('Probíhá po částech — zbývá: ' + ($missingLabels -join '; ')) } }
return @{ Code='UPDATE_NEEDED'; Tag='[!]'; Color='Yellow'; Label='Nutná aktualizace certifikátů' } return @{ Code='UPDATE_NEEDED'; Tag='[!]'; Color='Yellow'; Label='Nutná aktualizace certifikátů' }
} }
@@ -506,30 +585,58 @@ function Get-RemediationCategory {
function Invoke-Detection { function Invoke-Detection {
param([bool]$SkipBootMgrFile) param([bool]$SkipBootMgrFile)
$os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
$R = [ordered]@{ $osBuildFull = try {
AuditTimestamp=(Get-Date).ToString('yyyy-MM-dd HH:mm:ss'); Hostname=$env:COMPUTERNAME $cv = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction Stop
OSCaption=if($os){$os.Caption}else{$null}; OSBuild=if($os){$os.BuildNumber}else{$null} '{0}.{1}' -f $cv.CurrentBuildNumber, $cv.UBR
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{'?'} }) } catch {
EnvironmentType=Get-EnvironmentType; Hardware=Get-HardwareInfo; SecureBoot=Get-SecureBootState if ($os) { [string]$os.BuildNumber } else { '?' }
OperatingMode='Unknown'; BitLocker=$null; Certificates=$null; Registry=Get-RegistryStatus
EventLog=Get-EventLogStatus; TaskExists=Get-TaskExists; BootManager=$null; Phases=$null
Category=$null; CategoryLabel=$null; CategoryColor=$null
} }
$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
Registry = Get-RegistryStatus
EventLog = Get-EventLogStatus
TaskExists = Get-TaskExists
BootManager = $null
Phases = $null
Category = $null
CategoryLabel = $null
CategoryColor = $null
}
if ($R.SecureBoot.IsUEFI -and $R.SecureBoot.IsSupported) { if ($R.SecureBoot.IsUEFI -and $R.SecureBoot.IsSupported) {
$R.OperatingMode = Get-SecureBootMode $R.OperatingMode = Get-SecureBootMode
$R.Certificates = Get-CertificateStatus $R.Certificates = Get-CertificateStatus
$R.BootManager = Get-BootManagerStatus -SkipFileCheck:$SkipBootMgrFile $R.BootManager = Get-BootManagerStatus -SkipFileCheck:$SkipBootMgrFile
} else { } else {
$R.Certificates = [ordered]@{ KEK=[ordered]@{Has2011=$false;Has2023=$false;Certs2011=@();Certs2023=@();Error='SB nedostupný'} $R.Certificates = [ordered]@{
DB=[ordered]@{Has2011UEFI=$false;Has2011WindowsPCA=$false;Has2023UEFI=$false;Has2023OptionROM=$false;Has2023WindowsUEFI=$false;Certs2011=@();Certs2023=@();Error='SB nedostupný'} KEK = [ordered]@{ Has2011=$false; Has2023=$false; Certs2011=@(); Certs2023=@(); Error='SB nedostupný' }
DbxRevokesPCA2011=$false;AnyExpiring2011=$false } DB = [ordered]@{ Has2011UEFI=$false; Has2011WindowsPCA=$false; Has2023UEFI=$false
Has2023OptionROM=$false; Has2023WindowsUEFI=$false
Certs2011=@(); Certs2023=@(); Error='SB nedostupný' }
DbxRevokesPCA2011=$false; AnyExpiring2011=$false
}
$R.BootManager = [ordered]@{ EspHas2023=$false; Evidence=@(); Chain=$null; Signer=$null; Error='SB nedostupný'; Checked=$false } $R.BootManager = [ordered]@{ EspHas2023=$false; Evidence=@(); Chain=$null; Signer=$null; Error='SB nedostupný'; Checked=$false }
} }
$R.BitLocker = Get-BitLockerInfo $R.BitLocker = Get-BitLockerInfo
$R.Phases = Build-Phases -Sb $R.SecureBoot -Cert $R.Certificates -Boot $R.BootManager -TaskExists $R.TaskExists -Reg $R.Registry -Evt $R.EventLog $R.Phases = Build-Phases -Sb $R.SecureBoot -Cert $R.Certificates -Boot $R.BootManager `
-TaskExists $R.TaskExists -Reg $R.Registry -Evt $R.EventLog
$cat = Get-RemediationCategory -Result $R $cat = Get-RemediationCategory -Result $R
$R.Category=$cat.Code; $R.CategoryLabel="$($cat.Tag) $($cat.Label)"; $R.CategoryColor=$cat.Color $R.Category = $cat.Code
$R.CategoryLabel = "$($cat.Tag) $($cat.Label)"
$R.CategoryColor = $cat.Color
return $R return $R
} }
@@ -546,12 +653,16 @@ function Show-DetectionSummary {
Write-KV 'Prostředí' $R.EnvironmentType 'White' ("· {0} {1}" -f $hw.Manufacturer, $hw.Model) Write-KV 'Prostředí' $R.EnvironmentType 'White' ("· {0} {1}" -f $hw.Manufacturer, $hw.Model)
if ($sb.IsUEFI -and $sb.IsSupported) { if ($sb.IsUEFI -and $sb.IsSupported) {
$modeColor = if ($R.OperatingMode -eq 'Setup') { 'Red' } elseif ($R.OperatingMode -in @('User','Deployed')) { 'White' } else { 'Yellow' } $modeColor = if ($R.OperatingMode -eq 'Setup') { 'Red' } elseif ($R.OperatingMode -in @('User','Deployed')) { 'White' } else { 'Yellow' }
Write-KV 'Secure Boot' ("zapnuto · mode {0}" -f $R.OperatingMode) $(if($sb.IsEnabled){'White'}else{'Yellow'}) $(if($R.OperatingMode -eq 'Setup'){'· Setup Mode = nelze dokončit!'}) $modeNote = if ($R.OperatingMode -eq 'Setup') { '· Setup Mode = nelze dokončit!' } else { $null }
Write-KV 'Secure Boot' ("zapnuto · mode {0}" -f $R.OperatingMode) $modeColor $modeNote
} }
if ($bl -and $bl.Status -in @('On','Off','1','2','0')) { if ($bl -and $bl.Status -in @('On','Off','1','2','0')) {
$blOn = ($bl.Status -eq 'On' -or $bl.Status -eq '1' -or $bl.Status -eq '2') $blOn = ($bl.Status -eq 'On' -or $bl.Status -eq '1' -or $bl.Status -eq '2')
$note = if ($blOn -and $bl.UsesPcr) { '· PCR/TPM — před restartem ověř recovery key!' } elseif ($blOn) { '· chráněno' } else { '' } $blRisk = $blOn -and $bl.UsesPcr
Write-KV 'BitLocker' ("{0} [{1}]" -f $bl.Status, $bl.Protectors) $(if($blOn -and $bl.UsesPcr){'Yellow'}else{'White'}) $note $(if($blOn -and $bl.UsesPcr){'Yellow'}else{'DarkGray'}) $blNote = if ($blRisk) { '· PCR/TPM — před restartem ověř recovery key!' } elseif ($blOn) { '· chráněno' } else { '' }
$blColor = if ($blRisk) { 'Yellow' } else { 'White' }
$noteColor = if ($blRisk) { 'Yellow' } else { 'DarkGray' }
Write-KV 'BitLocker' ("{0} [{1}]" -f $bl.Status, $bl.Protectors) $blColor $blNote $noteColor
} }
if ($Prereqs) { Show-Prerequisites -Prereqs $Prereqs } if ($Prereqs) { Show-Prerequisites -Prereqs $Prereqs }
@@ -611,42 +722,41 @@ function Show-DetailedDetection {
function Resolve-RemediationPlan { function Resolve-RemediationPlan {
param($R, [bool]$ForceMode) param($R, [bool]$ForceMode)
$plan=[ordered]@{ Applicable=$false; AskUser=$false; Caution=$null; Reason=$null; NextSteps=@() } $plan=[ordered]@{ Applicable=$false; Caution=$null; Reason=$null; NextSteps=@() }
switch -Wildcard ($R.Category) { switch -Wildcard ($R.Category) {
'OK' { $plan.Reason='Server je kompletně hotový — žádná akce.'; if ($ForceMode){ $plan.Applicable=$true; $plan.AskUser=$true } } 'OK' { $plan.Reason='Server je kompletně hotový — žádná akce.'; if ($ForceMode){ $plan.Applicable=$true } }
'OK_TRANSITION' { 'OK_TRANSITION' {
$plan.Reason='2023 certifikáty i aktivní Boot Manager jsou nasazeny. Staré 2011 zůstávají (normální). Žádná akce.' $plan.Reason='2023 certifikáty i aktivní Boot Manager jsou nasazeny. Staré 2011 zůstávají (normální). Žádná akce.'
$plan.NextSteps=@('Nepovinné: časem ověřte revokaci starého boot manageru (DBX).') $plan.NextSteps=@('Nepovinné: časem ověřte revokaci starého boot manageru (DBX).')
if ($ForceMode){ $plan.Applicable=$true; $plan.AskUser=$true } if ($ForceMode){ $plan.Applicable=$true }
} }
'UPDATE_NEEDED' { $plan.Applicable=$true; $plan.AskUser=$true; $plan.Reason='Lze zahájit aktualizaci metodou registry (KB5068202).' } 'UPDATE_NEEDED' { $plan.Applicable=$true; $plan.Reason='Lze zahájit aktualizaci metodou registry (KB5068202).' }
'UPDATE_PARTIAL' { $plan.Applicable=$true; $plan.AskUser=$true; $plan.Reason='Část už nasazena, zbytek se aplikuje v dalším cyklu.'; $plan.Caution='Po nastavení a tasku bude potřeba RESTART; opakujte, dokud nebude HOTOVO.' } 'UPDATE_PARTIAL' { $plan.Applicable=$true; $plan.Reason='Část už nasazena, zbytek se aplikuje v dalším cyklu.'; $plan.Caution='Po nastavení a tasku bude potřeba RESTART; opakujte, dokud nebude HOTOVO.' }
'UPDATE_PENDING' { 'UPDATE_PENDING' {
$au = [int]$R.Registry.AvailableUpdates $au = [int]$R.Registry.AvailableUpdates
# „Staged" = Boot Manager je už připravený k aktivaci (2023 na ESP, nebo AvailableUpdates # 0x4100 = Boot Manager je staged na ESP — stačí RESTART, task nespouštět.
# v boot-manager fázi 0x4100/0x4000). Pak STAČÍ RESTART. Jinak je nutné ho teprve naplánovat. # Vše ostatní (0x4000, 0x0, …) — spustit task, který posune proces dál.
$staged = ([bool]$R.BootManager.EspHas2023) -or ($au -eq 0x4100) -or ($au -eq 0x4000) if ($au -eq 0x4100) {
if ($staged) { $plan.Reason = 'Boot Manager 2023 je staged (AvailableUpdates=0x4100). Aktivuje se RESTARTEM — task spouštět netřeba.'
$plan.Reason='Boot Manager je připravený (staged). Aktivuje se SAMOTNÝM RESTARTEM — víc nastavovat netřeba.' $plan.NextSteps = @('RESTARTUJTE server.', 'BitLocker (PCR7): před restartem ověřte recovery key.', 'Po restartu spusťte kontrolu znovu.')
$plan.NextSteps=@('RESTARTUJTE server (stačí restart, nic dalšího nenastavujte).','BitLocker (PCR7): před restartem ověřte recovery key.','Po restartu spusťte kontrolu — měl by být zelený HOTOVO.')
} else { } else {
$plan.Applicable=$true; $plan.AskUser=$true $plan.Applicable = $true
$plan.Reason='KEK i DB hotové, ale Boot Manager ještě NENÍ připravený (AvailableUpdates=0x0, ESP není 2023). Je třeba nastavit registry + spustit task a teprve POTÉ restartovat.' $plan.Reason = ('KEK i DB hotové, Boot Manager v procesu (AvailableUpdates=0x{0:X}). Task posune proces dál.' -f $au)
$plan.Caution='Tímto se naplánuje Boot Manager; aktivuje se až RESTARTEM (re-aplikace už hotových certifikátů je neškodná).' $plan.Caution = 'Po dokončení tasku bude nutný RESTART pro aktivaci Boot Manageru.'
$plan.NextSteps=@('Po tomto kroku RESTARTUJTE server.','Po restartu spusťte kontrolu znovu (nebo -RegisterResume).') $plan.NextSteps = @('Po dokončení tasku RESTARTUJTE server.', 'Po restartu spusťte kontrolu znovu.')
} }
} }
'UPDATE_FAILED' { 'UPDATE_FAILED' {
if ($R.EventLog.ById[1795]) { if ($R.EventLog.ById[1795]) {
$plan.Reason='Selhání Event 1795 (chyba firmwaru). Příčina je ve firmwaru/hostiteli.' $plan.Reason='Selhání Event 1795 (chyba firmwaru). Příčina je ve firmwaru/hostiteli.'
$plan.NextSteps=@('Aktualizujte firmware / Hyper-V hostitele (KB5085790).','Poté spusťte kontrolu znovu.') $plan.NextSteps=@('Aktualizujte firmware / Hyper-V hostitele (KB5085790).','Poté spusťte kontrolu znovu.')
if ($ForceMode){ $plan.Applicable=$true; $plan.AskUser=$true; $plan.Caution='Event 1795 = problém firmwaru; -Force jen zopakuje pokus.' } if ($ForceMode){ $plan.Applicable=$true; $plan.Caution='Event 1795 = problém firmwaru; -Force jen zopakuje pokus.' }
} else { $plan.Applicable=$true; $plan.AskUser=$true; $plan.Reason='Předchozí pokus selhal. Lze zopakovat.'; $plan.Caution='Zkontrolujte UEFICA2023ErrorEvent výše.' } } else { $plan.Applicable=$true; $plan.Reason='Předchozí pokus selhal. Lze zopakovat.'; $plan.Caution='Zkontrolujte UEFICA2023ErrorEvent výše.' }
} }
'FIRMWARE_UPDATE_NEEDED' { 'FIRMWARE_UPDATE_NEEDED' {
$plan.Reason='Aktualizace pozastavena (Temporarily Paused / Event 1802) — známý problém firmwaru.' $plan.Reason='Aktualizace pozastavena (Temporarily Paused / Event 1802) — známý problém firmwaru.'
$plan.NextSteps=@('Aktualizujte firmware u výrobce (Dell/HP/Lenovo/Supermicro).','Poté spusťte kontrolu znovu.') $plan.NextSteps=@('Aktualizujte firmware u výrobce (Dell/HP/Lenovo/Supermicro).','Poté spusťte kontrolu znovu.')
if ($ForceMode){ $plan.Applicable=$true; $plan.AskUser=$true; $plan.Caution='Pozastaveno firmwarem; -Force se pokusí i tak.' } if ($ForceMode){ $plan.Applicable=$true; $plan.Caution='Pozastaveno firmwarem; -Force se pokusí i tak.' }
} }
'NOT_SUPPORTED' { 'NOT_SUPPORTED' {
$plan.Reason='ConfidenceLevel: Not Supported — zařízení nepodporuje automatickou cestu.' $plan.Reason='ConfidenceLevel: Not Supported — zařízení nepodporuje automatickou cestu.'
@@ -684,6 +794,37 @@ function Get-UserConsent {
} catch { return ((Read-Host 'Aplikovat? [a/N]') -match '^(a|ano|y|yes)$') } } catch { return ((Read-Host 'Aplikovat? [a/N]') -match '^(a|ano|y|yes)$') }
} }
function Invoke-RestartOffer {
# Nabídne uživateli restart. Skript nikdy nerestartuje automaticky — vždy interaktivní dotaz.
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 Read-Host)'
}
}
}
#endregion #endregion
#region ── Stav napříč restarty + RunOnce ───────────────────────────────────── #region ── Stav napříč restarty + RunOnce ─────────────────────────────────────
@@ -715,33 +856,30 @@ function Register-Resume {
function Invoke-Remediation { function Invoke-Remediation {
param([bool]$SkipBootMgrFile) param([bool]$SkipBootMgrFile)
$out=[ordered]@{ Status='Unknown'; Message=''; After=$null; LogFile=$script:LogFile } $out=[ordered]@{ Status='Unknown'; Message=''; After=$null }
Write-Head 'REMEDIACE'; Add-LogLine 'REMEDIACE START' Write-Head 'REMEDIACE'; Add-LogLine 'REMEDIACE START'
# Přečti AvailableUpdates ještě PŘED zápisem — rozhodnutí, jestli ho nastavit nebo zachovat. # AvailableUpdates se nastavuje na 0x5944 POUZE jednou — při prvním spuštění (null) nebo po
$auBefore = (Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue).AvailableUpdates # dokončení předchozího cyklu (0). Systém si pak hodnotu sám spravuje: odečítá bity jak postupuje
# "In progress" = nenulová hodnota → systém ji spravuje sám (dle KB5068202 nastavit jen jednou). # (0x5944→0x5904→0x5104→0x4104→0x4100→0x4000→0x0). Skript ji neresetuje — jen čte a interpretuje.
# Null nebo 0 → první start nebo re-trigger (Boot Manager není aktivní přesto, že certifikáty jsou OK). $auCurrent = (Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue).AvailableUpdates
$auInProgress = ($null -ne $auBefore) -and ([int]$auBefore -ne 0) $auNeedsInit = ($null -eq $auCurrent) -or ([int]$auCurrent -eq 0)
$auLabel = if ($auInProgress) { '0x{0:X} zachováno (probíhá)' -f [int]$auBefore } else { '0x5944 (nové/reset)' } $auInfo = if ($auNeedsInit) { '0x5944 (první inicializace)' } else { '0x{0:X} zachováno (probíhá)' -f [int]$auCurrent }
Write-Host (" [1/3] Nastavuji registry (MicrosoftUpdateManagedOptIn=1, AvailableUpdates={0}) ... " -f $auLabel) -NoNewline Write-Host (" [1/3] Nastavuji registry (MicrosoftUpdateManagedOptIn=1, AvailableUpdates={0}) ... " -f $auInfo) -NoNewline
try { try {
if (-not (Test-Path $REG_SECUREBOOT)) { New-Item -Path $REG_SECUREBOOT -Force -ErrorAction Stop | Out-Null } 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 Set-ItemProperty -Path $REG_SECUREBOOT -Name 'MicrosoftUpdateManagedOptIn' -Value 1 -Type DWord -Force -ErrorAction Stop
# AvailableUpdates nastavujeme na 0x5944 POUZE pokud ještě nebyl nastaven (null) nebo je 0. if ($auNeedsInit) {
# Pokud je nenulový, hodnotu zachováme — resetování by smazalo již dosažený postup.
if (-not $auInProgress) {
Set-ItemProperty -Path $REG_SECUREBOOT -Name 'AvailableUpdates' -Value $AVAILABLE_UPDATES_VALUE -Type DWord -Force -ErrorAction Stop Set-ItemProperty -Path $REG_SECUREBOOT -Name 'AvailableUpdates' -Value $AVAILABLE_UPDATES_VALUE -Type DWord -Force -ErrorAction Stop
$vCheck = (Get-ItemProperty $REG_SECUREBOOT -ErrorAction Stop).AvailableUpdates
if ([int]$vCheck -ne $AVAILABLE_UPDATES_VALUE) { throw ('Ověření selhalo — AvailableUpdates=0x{0:X}, očekáváno 0x5944' -f [int]$vCheck) }
} }
$o = Get-ItemProperty $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -ErrorAction SilentlyContinue $o = Get-ItemProperty $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -ErrorAction SilentlyContinue
if ($o -and $o.HighConfidenceOptOut -ne 0) { Set-ItemProperty -Path $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -Value 0 -Type DWord -Force -ErrorAction Stop } if ($o -and $o.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 } if (-not (Test-Path $REG_SERVICING)) { New-Item -Path $REG_SERVICING -Force -ErrorAction Stop | Out-Null }
$vNow = (Get-ItemProperty $REG_SECUREBOOT -ErrorAction Stop).AvailableUpdates
$vExpected = if ($auInProgress) { [int]$auBefore } else { $AVAILABLE_UPDATES_VALUE }
if ([int]$vNow -ne $vExpected) { throw ('Ověření selhalo — AvailableUpdates=0x{0:X}, očekáváno 0x{1:X}' -f [int]$vNow, $vExpected) }
Write-Host 'hotovo' -ForegroundColor Green Write-Host 'hotovo' -ForegroundColor Green
$auAction = if ($auInProgress) { 'zachováno' } else { 'nastaveno' } $auLogVal = if ($auNeedsInit) { '0x5944 (nastaveno)' } else { '0x{0:X} (zachováno)' -f [int]$auCurrent }
Add-LogLine ('Krok 1: MicrosoftUpdateManagedOptIn=1, AvailableUpdates=0x{0:X} ({1})' -f [int]$vNow, $auAction) Add-LogLine ("Krok 1: MicrosoftUpdateManagedOptIn=1, AvailableUpdates={0}" -f $auLogVal)
} catch { Write-Host 'CHYBA' -ForegroundColor Red; Write-Line (" {0}" -f $_.Exception.Message) Red; $out.Status='Error'; $out.Message="Zápis registry selhal: $($_.Exception.Message)"; return $out } } catch { Write-Host 'CHYBA' -ForegroundColor Red; Write-Line (" {0}" -f $_.Exception.Message) Red; $out.Status='Error'; $out.Message="Zápis registry selhal: $($_.Exception.Message)"; return $out }
if ($SkipScheduledTask) { Write-Line ' [2/3] Servicing task přeskočen (-SkipScheduledTask).' DarkGray } if ($SkipScheduledTask) { Write-Line ' [2/3] Servicing task přeskočen (-SkipScheduledTask).' DarkGray }
@@ -773,7 +911,16 @@ function Invoke-Remediation {
$after=Invoke-Detection -SkipBootMgrFile:$SkipBootMgrFile $after=Invoke-Detection -SkipBootMgrFile:$SkipBootMgrFile
Write-Host 'hotovo' -ForegroundColor Green; Write-Host '' Write-Host 'hotovo' -ForegroundColor Green; Write-Host ''
Write-Line (' AvailableUpdates: {0}' -f (Get-AvailableUpdatesText $after.Registry.AvailableUpdates)) Cyan Write-Line (' AvailableUpdates: {0}' -f (Get-AvailableUpdatesText $after.Registry.AvailableUpdates)) Cyan
foreach ($k in @('Kek2023','Db2023Windows','BootManager2023')) { $p=$after.Phases[$k]; Write-Check -State $p.State -Text $p.Label -Note $p.Note } Write-Line (' UEFICA2023Status : {0}' -f $after.Registry.UEFICA2023StatusText) $(if($after.Registry.UEFICA2023StatusText -eq 'Updated'){'Green'}else{'White'})
foreach ($k in @('Kek2023','Db2023Windows','CertServicingStatus','BootManager2023')) { $p=$after.Phases[$k]; Write-Check -State $p.State -Text $p.Label -Note $p.Note }
$afterAU = [int]$after.Registry.AvailableUpdates
if ($afterAU -eq 0x4100) {
Write-Host ''
Write-Host ' *** Boot Manager 2023 je staged (AvailableUpdates=0x4100) ***' -ForegroundColor Yellow
Write-Host ' Aktivace proběhne po restartu serveru.' -ForegroundColor Yellow
Add-LogLine 'Post-remediace: AU=0x4100 — BM staged, vyžaduje restart'
Invoke-RestartOffer
}
$out.Status='Applied'; $out.After=$after; $out.Message='Registry nastavena, task zpracován. Boot Manager se aktivuje až po restartu.' $out.Status='Applied'; $out.After=$after; $out.Message='Registry nastavena, task zpracován. Boot Manager se aktivuje až po restartu.'
Add-LogLine 'REMEDIACE KONEC: Applied' Add-LogLine 'REMEDIACE KONEC: Applied'
return $out return $out
@@ -791,8 +938,8 @@ function Get-StageInfo {
0x5904 { return @{ Step=2; Total=6; Label='Windows UEFI CA 2023 v DB' } } 0x5904 { return @{ Step=2; Total=6; Label='Windows UEFI CA 2023 v DB' } }
0x5104 { return @{ Step=3; Total=6; Label='Option ROM UEFI CA 2023 v DB' } } 0x5104 { return @{ Step=3; Total=6; Label='Option ROM UEFI CA 2023 v DB' } }
0x4104 { return @{ Step=4; Total=6; Label='Microsoft UEFI CA 2023 v DB' } } 0x4104 { return @{ Step=4; Total=6; Label='Microsoft UEFI CA 2023 v DB' } }
0x4100 { return @{ Step=5; Total=6; Label='Boot Manager nasazen na ESP' } } 0x4100 { return @{ Step=5; Total=6; Label='Boot Manager nasazen na ESP — RESTART' } }
0x4000 { return @{ Step=6; Total=6; Label='čeká na finální restart' } } 0x4000 { return @{ Step=6; Total=6; Label='guard bit — task dokončuje' } }
0 { return @{ Step=6; Total=6; Label='dokončeno' } } 0 { return @{ Step=6; Total=6; Label='dokončeno' } }
default { return @{ Step=1; Total=6; Label=('mezistav 0x{0:X}' -f [int]$v) } } default { return @{ Step=1; Total=6; Label=('mezistav 0x{0:X}' -f [int]$v) } }
} }
@@ -823,19 +970,50 @@ function Get-Prerequisites {
param($R, [bool]$IsAdmin) param($R, [bool]$IsAdmin)
$sb = $R.SecureBoot $sb = $R.SecureBoot
$list = @() $list = @()
$list += @{ Label='Spuštěno jako Administrator'; Ok=$IsAdmin; Hard=$true; Note=$(if(-not $IsAdmin){'spusťte PowerShell „Run as administrator"'}) }
$list += @{ Label='UEFI + Secure Boot podporováno'; Ok=($sb.IsUEFI -and $sb.IsSupported); Hard=$true; Note=$(if(-not ($sb.IsUEFI -and $sb.IsSupported)){'Legacy BIOS / nepodporováno'}) } $list += @{ Label='Spuštěno jako Administrator'
$list += @{ Label='Secure Boot zapnutý'; Ok=[bool]$sb.IsEnabled; Hard=$true; Note=$(if($sb.IsUEFI -and $sb.IsSupported -and -not $sb.IsEnabled){'zapněte v UEFI'}) } Ok=$IsAdmin; Hard=$true
$list += @{ Label='Ne Setup Mode (enrolled PK)'; Ok=($R.OperatingMode -ne 'Setup'); Hard=$true; Note=$(if($R.OperatingMode -eq 'Setup'){'obnovte Secure Boot klíče v UEFI'}) } Note=if(-not $IsAdmin){'spusťte PowerShell „Run as administrator"'} }
$buildNote = if ($R.TaskExists) { "build $($R.OSBuildFull)" } else { "build $($R.OSBuildFull) — chybí task, nainstalujte 10/2025+ (KB5066835)" }
$list += @{ Label='Build s podporou aktualizace (servicing task)'; Ok=[bool]$R.TaskExists; Hard=$true; Note=$buildNote } $sbOk = $sb.IsUEFI -and $sb.IsSupported
# Boot-manager servicing přítomný? Když jsou certy v DB/KEK, ale WindowsUEFICA2023Capable i $list += @{ Label='UEFI + Secure Boot podporováno'
# UEFICA2023Status chybí a boot manager není aktivní → build je příliš starý (nutný Windows Update). Ok=$sbOk; Hard=$true
Note=if(-not $sbOk){'Legacy BIOS / nepodporováno'} }
$list += @{ Label='Secure Boot zapnutý'
Ok=[bool]$sb.IsEnabled; Hard=$true
Note=if($sbOk -and -not $sb.IsEnabled){'zapněte v UEFI'} }
$list += @{ Label='Ne Setup Mode (enrolled PK)'
Ok=($R.OperatingMode -ne 'Setup'); Hard=$true
Note=if($R.OperatingMode -eq 'Setup'){'obnovte Secure Boot klíče v UEFI'} }
$buildNote = if ($R.TaskExists) {
"build $($R.OSBuildFull)"
} else {
"build $($R.OSBuildFull) — chybí task, nainstalujte 10/2025+ (KB5066835)"
}
$list += @{ Label='Build s podporou aktualizace (servicing task)'
Ok=[bool]$R.TaskExists; Hard=$true; Note=$buildNote }
# Starý build: certy jsou v KEK/DB, ale WindowsUEFICA2023Capable + UEFICA2023Status zcela chybí
# a BM není staged → servicing infrastruktura pro Boot Manager neexistuje, nutný Windows Update.
$auP = [int]$R.Registry.AvailableUpdates $auP = [int]$R.Registry.AvailableUpdates
$bmStagedP = ([bool]$R.BootManager.EspHas2023) -or ($auP -eq 0x4100) -or ($auP -eq 0x4000) $bmStagedP = ([bool]$R.BootManager.EspHas2023) -or ($auP -eq 0x4100) -or ($auP -eq 0x4000)
$staleServ = ($R.Phases['Kek2023'].Done -or $R.Phases['Db2023Windows'].Done) -and (-not $R.Phases['BootManager2023'].Done) -and (-not $bmStagedP) -and ($null -eq $R.Registry.WindowsUEFICA2023Capable) -and ($null -eq $R.Registry.UEFICA2023Status) $staleServ = ($R.Phases['Kek2023'].Done -or $R.Phases['Db2023Windows'].Done) `
$list += @{ Label='Servicing pro Boot Manager 2023 (aktuální build)'; Ok=(-not $staleServ); Hard=$true; Note=$(if($staleServ){"build $($R.OSBuildFull) je starý — chybí 2023 boot-manager servicing, nutný Windows Update"}) } -and (-not $R.Phases['BootManager2023'].Done) `
$list += @{ Label='Zařízení není „Not Supported"'; Ok=($R.Registry.ConfidenceLevel -notlike 'Not Supported*'); Hard=$false; Note=$(if($R.Registry.ConfidenceLevel -like 'Not Supported*'){'ConfidenceLevel: Not Supported — řešení u OEM'}) } -and (-not $bmStagedP) `
-and ($null -eq $R.Registry.WindowsUEFICA2023Capable) `
-and ($null -eq $R.Registry.UEFICA2023Status)
$list += @{ Label='Servicing pro Boot Manager 2023 (aktuální build)'
Ok=(-not $staleServ); Hard=$true
Note=if($staleServ){"build $($R.OSBuildFull) je starý — chybí 2023 boot-manager servicing, nutný Windows Update"} }
$notSupported = $R.Registry.ConfidenceLevel -like 'Not Supported*'
$list += @{ Label='Zařízení není „Not Supported"'
Ok=(-not $notSupported); Hard=$false
Note=if($notSupported){'ConfidenceLevel: Not Supported — řešení u OEM'} }
return $list return $list
} }
@@ -901,6 +1079,10 @@ $remediation = $null
if (-not $plan.Applicable) { if (-not $plan.Applicable) {
if ($plan.NextSteps.Count) { Write-Host ''; Write-Host ' Další kroky:' -ForegroundColor White; $i=1; foreach ($s in $plan.NextSteps) { Write-Host (" {0}. {1}" -f $i,$s) -ForegroundColor Gray; $i++ } } if ($plan.NextSteps.Count) { Write-Host ''; Write-Host ' Další kroky:' -ForegroundColor White; $i=1; foreach ($s in $plan.NextSteps) { Write-Host (" {0}. {1}" -f $i,$s) -ForegroundColor Gray; $i++ } }
elseif ($result.Category -like 'OK*') { Write-Host ' Není potřeba žádná akce.' -ForegroundColor Green } elseif ($result.Category -like 'OK*') { Write-Host ' Není potřeba žádná akce.' -ForegroundColor Green }
# UPDATE_PENDING na 0x4100: Boot Manager staged — nabídnout restart přímo z detekce
if ($result.Category -eq 'UPDATE_PENDING' -and -not $CheckOnly -and -not $isWhatIf -and $isAdmin) {
if ([int]$result.Registry.AvailableUpdates -eq 0x4100) { Invoke-RestartOffer }
}
} else { } else {
if ($plan.Caution) { Write-Host (" Pozor: {0}" -f $plan.Caution) -ForegroundColor Yellow } if ($plan.Caution) { Write-Host (" Pozor: {0}" -f $plan.Caution) -ForegroundColor Yellow }
if (-not $isAdmin) { Write-Host ''; Write-Host ' Remediaci nelze provést bez práv Administrator.' -ForegroundColor Yellow } if (-not $isAdmin) { Write-Host ''; Write-Host ' Remediaci nelze provést bez práv Administrator.' -ForegroundColor Yellow }
@@ -928,7 +1110,7 @@ if ($remediation -and $remediation.Status -eq 'Applied') {
else { else {
Write-Host ' ČÁSTEČNĚ HOTOVO — proces pokračuje po restartu.' -ForegroundColor Yellow Write-Host ' ČÁSTEČNĚ HOTOVO — proces pokračuje po restartu.' -ForegroundColor Yellow
Write-Host ''; Write-Host ' Další kroky:' -ForegroundColor White Write-Host ''; Write-Host ' Další kroky:' -ForegroundColor White
Write-Host ' 1. Naplánujte RESTART serveru (skript jej záměrně neprovedl).' -ForegroundColor Gray Write-Host ' 1. RESTART serveru (byl nabídnut výše; pokud jste odmítli, restartujte ručně).' -ForegroundColor Gray
if ($after.BitLocker -and $after.BitLocker.UsesPcr -and ($after.BitLocker.Status -eq 'On' -or $after.BitLocker.Status -eq '1' -or $after.BitLocker.Status -eq '2')) { if ($after.BitLocker -and $after.BitLocker.UsesPcr -and ($after.BitLocker.Status -eq 'On' -or $after.BitLocker.Status -eq '1' -or $after.BitLocker.Status -eq '2')) {
Write-Host ' 2. BitLocker (PCR/TPM) je aktivní — před restartem ověřte recovery key!' -ForegroundColor Yellow Write-Host ' 2. BitLocker (PCR/TPM) je aktivní — před restartem ověřte recovery key!' -ForegroundColor Yellow
} else { Write-Host ' 2. BitLocker s PCR7: před restartem ověřte recovery key.' -ForegroundColor Gray } } else { Write-Host ' 2. BitLocker s PCR7: před restartem ověřte recovery key.' -ForegroundColor Gray }