diff --git a/Invoke-SecureBootRemediation.ps1 b/Invoke-SecureBootRemediation.ps1 index b941287..456d725 100644 --- a/Invoke-SecureBootRemediation.ps1 +++ b/Invoke-SecureBootRemediation.ps1 @@ -6,45 +6,44 @@ .DESCRIPTION Sloučení detekčního a remediačního skriptu do jednoho lokálního průvodce s fázovým checklistem. Server prohlásí za HOTOVÝ teprve když jsou splněné VŠECHNY povinné fáze — - včetně ověření, že Boot Manager (bootmgfw.efi) je reálně podepsaný Windows UEFI CA 2023. - Pouhá přítomnost certifikátů v KEK/DB nestačí (to byl důvod předčasného „OK"). + včetně toho, že se systém reálně bootuje z Boot Manageru podepsaného Windows UEFI CA 2023 + (signalizuje WindowsUEFICA2023Capable=2 / Event 1808 / 1799). - Fáze (checklist): - 1. Secure Boot zapnutý - 2. Servicing task k dispozici - 3. KEK obsahuje Microsoft Corporation KEK 2K CA 2023 - 4. DB obsahuje Windows UEFI CA 2023 - 5. Boot Manager podepsaný Windows UEFI CA 2023 (mount ESP + certutil / Event 1799) - + volitelné: 3rd-party UEFI CA 2023, Option ROM 2023, DBX revokace PCA 2011 + Respektuje oficiální MS guidance (autoritativní hodnoty): + - UEFICA2023Status (REG_SZ): NotStarted / InProgress / Updated + - WindowsUEFICA2023Capable: 0=cert není v DB, 1=cert v DB, 2=bootuje se z 2023 boot manageru + - ConfidenceLevel (REG_SZ): High Confidence / Temporarily Paused / Not Supported / ... + - AvailableUpdates: 0x5944 -> 0x5904 -> 0x5104 -> 0x4104 -> 0x4100 -> 0x4000 -> 0x0 + - Cílový stav: AvailableUpdates=0x0, UEFICA2023Status=Updated, WindowsUEFICA2023Capable=2 + - Events (TPM-WMI): 1808=hotovo, 1799=boot mgr 2023, 1795=chyba firmwaru, + 1802=pozastaveno, 1803=odloženo (monitor), 1801=zatím neaplikováno (monitor) Skript ZÁMĚRNĚ NERESTARTUJE server. Sleduje stav i napříč restarty (state.json) a umí volitelně po dalším restartu spustit kontrolu automaticky (-RegisterResume) — stále BEZ auto-restartu. .PARAMETER CheckOnly - Jen detekce + checklist, žádné změny ani dotaz. Nastaví exit kód: 0=hotovo, 1=nutná akce, - 2=blokováno (firmware/OEM/Event 1803/1795). Vhodné pro RMM/monitoring a pro auto-recheck. + Jen detekce + checklist + exit kód (0=hotovo, 1=nutná akce, 2=blokováno). Bez změn/dotazu. .PARAMETER AssumeYes - Přeskočí interaktivní dotaz a remediaci rovnou aplikuje (pokud je smysluplná). Bez restartu. + Přeskočí dotaz a remediaci rovnou aplikuje (pokud je smysluplná). Bez restartu. .PARAMETER Force - Aplikuje remediaci i ve stavech, kdy ji skript jinak nedoporučuje (firmware nezpůsobilý, - už hotovo, Hyper-V Event 1795). + Aplikuje remediaci i ve stavech, kdy ji skript jinak nedoporučuje. .PARAMETER SkipScheduledTask Nastaví registry, ale nespustí servicing task (spustí se sám cca á 12 h). .PARAMETER SkipBootManagerFileCheck - Neověřuje podpis bootmgfw.efi přes mount ESP + certutil. Použije jen Event 1799. - Rychlejší a méně invazivní; o něco méně spolehlivé. + Neověřuje bootmgfw.efi na ESP přes mount+certutil. Dokončení se i tak pozná z + WindowsUEFICA2023Capable=2 / Event 1808 / 1799. .PARAMETER RegisterResume Po remediaci, kde zbývá restart, zaregistruje RunOnce, který po PŘÍŠTÍM (ručním) restartu - automaticky spustí tuto kontrolu (-CheckOnly). Nikdy nerestartuje sám. + automaticky spustí kontrolu (-CheckOnly). Nikdy nerestartuje sám. .PARAMETER Detailed - Rozšířený rozpis (jednotlivé certifikáty, posledních N událostí, boot manager chain). + Rozšířený rozpis (certifikáty, události, boot manager chain, bit-breakdown AvailableUpdates). .PARAMETER PassThru Vrátí výsledný objekt do pipeline. @@ -53,7 +52,7 @@ Cesta k logu. Default: log vedle skriptu vznikne jen při reálné remediaci. .NOTES - Reference: KB5062710, KB5068202, KB5085046, KB5085790. + Reference: KB5062710, KB5068202, KB5066835 (min. build 10/2025), KB5085790. Vyžaduje Administrator (čtení UEFI databází, mount ESP, zápis registry). #> @@ -85,21 +84,35 @@ try { $REG_SECUREBOOT = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot' $REG_SERVICING = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing' $REG_RUNONCE = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -$AVAILABLE_UPDATES_VALUE = 0x5944 # KB5068202: KEK + UEFI CA + Windows UEFI CA + Boot Manager +$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' +$WORK_ROOT = Join-Path $env:ProgramData 'SecureBootCA2023' +$STATE_FILE = Join-Path $WORK_ROOT 'state.json' -# Názvy certifikátů (pro X.509 i ASCII fallback detekci) $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' -# Symboly checklistu (stavěné z code-pointů kvůli odolnosti vůči kódování; lze změnit zde) +# Bity AvailableUpdates (dle CheckCA2023 / MS) — pro -Detailed rozpis +$AU_BITS = @( + @{ Bit=0x0002; Name='DBX revocation update' } + @{ Bit=0x0004; Name='KEK 2K CA 2023 -> KEK' } + @{ Bit=0x0020; Name='SkuSiPolicy update' } + @{ Bit=0x0040; Name='Windows UEFI CA 2023 -> DB' } + @{ Bit=0x0080; Name='PCA 2011 -> DBX (revoke old boot mgr)' } + @{ Bit=0x0100; Name='Boot manager update' } + @{ Bit=0x0200; Name='SVN firmware update' } + @{ Bit=0x0400; Name='SBAT firmware update' } + @{ Bit=0x0800; Name='Option ROM UEFI CA 2023 -> DB' } + @{ Bit=0x1000; Name='Microsoft UEFI CA 2023 -> DB' } + @{ Bit=0x4000; Name='Conditional CA 2023 guard bit' } +) + +# Symboly checklistu (z code-pointů kvůli odolnosti vůči kódování; lze změnit zde) $SYM_DONE = [string][char]0x2713 # ✓ $SYM_FAIL = [string][char]0x2717 # ✗ $SYM_PENDING = ' ' @@ -117,63 +130,42 @@ $script:LogActive = [bool]$LogPath function Add-LogLine { param([string]$Text) if (-not $script:LogActive) { return } - try { ('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Text) | - Out-File -FilePath $script:LogFile -Append -Encoding UTF8 -Force } catch { } + try { ('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Text) | Out-File -FilePath $script:LogFile -Append -Encoding UTF8 -Force } 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-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 ==" -} - -# Zvýrazněný řádek klíč → hodnota (hodnota barevně) function Write-KV { - param([string]$Key, [string]$Value, [string]$ValueColor = 'White', [string]$Note, [string]$NoteColor = 'DarkGray') + param([string]$Key, [string]$Value, [string]$ValueColor='White', [string]$Note, [string]$NoteColor='DarkGray') Write-Host (" {0,-16}: " -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) } - -# Řádek checklistu: [✓]/[ ]/[✗] + text + zvýrazněná poznámka function Write-Check { - param( - [ValidateSet('Done','Pending','Fail','Info')][string]$State, - [string]$Text, - [string]$Note, - [string]$NoteColor = 'Yellow' - ) + param([ValidateSet('Done','Pending','Fail','Info')][string]$State, [string]$Text, [string]$Note, [string]$NoteColor='Yellow') switch ($State) { - 'Done' { $mark = "[$SYM_DONE]"; $markColor = 'Green'; $textColor = 'White' } - 'Fail' { $mark = "[$SYM_FAIL]"; $markColor = 'Red'; $textColor = 'Red' } - 'Pending' { $mark = "[$SYM_PENDING]"; $markColor = 'DarkGray'; $textColor = 'Gray' } - 'Info' { $mark = "[$SYM_PENDING]"; $markColor = 'DarkGray'; $textColor = 'DarkGray' } + 'Done' { $mark="[$SYM_DONE]"; $mc='Green'; $tc='White' } + 'Fail' { $mark="[$SYM_FAIL]"; $mc='Red'; $tc='Red' } + 'Pending' { $mark="[$SYM_PENDING]"; $mc='DarkGray'; $tc='Gray' } + 'Info' { $mark="[$SYM_PENDING]"; $mc='DarkGray'; $tc='DarkGray' } } - Write-Host (" {0} " -f $mark) -ForegroundColor $markColor -NoNewline - Write-Host $Text -ForegroundColor $textColor -NoNewline + 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 ───────────────────────────── +#region ── Detekce — prostředí / HW / Secure Boot / mode / BitLocker ────────── function Get-EnvironmentType { try { - $cs = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop - $mfr = [string]$cs.Manufacturer - $model = [string]$cs.Model - if ($mfr -like '*VMware*') { return 'VMware VM' } + $cs = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop + $mfr=[string]$cs.Manufacturer; $model=[string]$cs.Model + if ($mfr -like '*VMware*') { return 'VMware VM' } if ($mfr -like '*Microsoft*' -and $model -eq 'Virtual Machine') { return 'Hyper-V VM' } if ($model -like '*Virtual*' -or $mfr -like '*QEMU*' -or $mfr -like '*Xen*') { return 'Other VM' } return 'Physical' @@ -181,275 +173,223 @@ function Get-EnvironmentType { } function Get-HardwareInfo { - $hw = [ordered]@{ Manufacturer='Unknown'; Model='Unknown'; SerialNumber='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 { } + $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 - $hw.SerialNumber = [string]$bios.SerialNumber + $hw.BiosVersion=[string]$bios.SMBIOSBIOSVersion if ($bios.ReleaseDate) { $hw.BiosReleaseDate = ([datetime]$bios.ReleaseDate).ToString('yyyy-MM-dd') } } catch { } - $fwTypeKey = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue - if ($fwTypeKey) { - $hw.FirmwareType = if ($fwTypeKey.PEFirmwareType -eq 2) { 'UEFI' } else { 'Legacy BIOS' } - } elseif (Test-Path $REG_SECUREBOOT) { $hw.FirmwareType = 'UEFI' } else { $hw.FirmwareType = 'Legacy BIOS' } + $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 { - $state = [ordered]@{ IsUEFI=$false; IsSupported=$false; IsEnabled=$false; ConfirmResult='Unknown'; Error=$null } - $fwTypeKey = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue - $state.IsUEFI = ($fwTypeKey -and $fwTypeKey.PEFirmwareType -eq 2) -or (Test-Path $REG_SECUREBOOT) - if (-not $state.IsUEFI) { return $state } - try { - $r = Confirm-SecureBootUEFI -ErrorAction Stop - $state.IsSupported = $true; $state.IsEnabled = [bool]$r; $state.ConfirmResult = $r.ToString() - } catch { - $msg = $_.Exception.Message - if ($msg -like '*not supported*' -or $msg -like '*Cmdlet not supported*') { - $state.IsSupported = $false; $state.ConfirmResult = 'NotSupported' - } elseif ($msg -like '*disabled*') { - $state.IsSupported = $true; $state.IsEnabled = $false; $state.ConfirmResult = 'Disabled' - } else { - $state.IsSupported = $true; $state.IsEnabled = $false; $state.ConfirmResult = 'Error'; $state.Error = $msg - } + $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 $state + return $s +} + +function Get-SecureBootMode { + # User / Deployed / Setup / Audit ze standardních UEFI proměnných + $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 (X.509 parsing + ASCII fallback) ───────────── +#region ── Detekce — certifikáty / registry / events / boot manager ─────────── function Parse-EFISignatureList { param([byte[]]$Bytes) - $certs = @() - if (-not $Bytes -or $Bytes.Length -lt 28) { return $certs } - $X509_GUID = [byte[]](0xa1,0x59,0xc0,0xa5, 0xe4,0x94, 0xa7,0x4a, 0x87,0xb5,0xab,0x15,0x5c,0x2b,0xf0,0x72) - $offset = 0 - while ($offset + 28 -le $Bytes.Length) { - $sigTypeGUID = $Bytes[$offset..($offset + 15)] - $sigListSize = [BitConverter]::ToUInt32($Bytes, $offset + 16) - $sigHeaderSize = [BitConverter]::ToUInt32($Bytes, $offset + 20) - $sigSize = [BitConverter]::ToUInt32($Bytes, $offset + 24) - if ($sigListSize -lt 28 -or $sigListSize -gt ($Bytes.Length - $offset)) { break } - $isX509 = $true - for ($i = 0; $i -lt 16; $i++) { if ($sigTypeGUID[$i] -ne $X509_GUID[$i]) { $isX509 = $false; break } } - if ($isX509 -and $sigSize -gt 16) { - $sigOffset = $offset + 28 + $sigHeaderSize - $listEnd = $offset + $sigListSize - while ($sigOffset + $sigSize -le $listEnd) { - $certOffset = $sigOffset + 16 - $certSize = [int]$sigSize - 16 - if ($certOffset + $certSize -le $Bytes.Length -and $certSize -gt 0) { - $certBytes = $Bytes[$certOffset..($certOffset + $certSize - 1)] - try { $certs += New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(, [byte[]]$certBytes) } catch { } + $certs=@(); if (-not $Bytes -or $Bytes.Length -lt 28) { return $certs } + $G=[byte[]](0xa1,0x59,0xc0,0xa5,0xe4,0x94,0xa7,0x4a,0x87,0xb5,0xab,0x15,0x5c,0x2b,0xf0,0x72) + $o=0 + while ($o + 28 -le $Bytes.Length) { + $gid=$Bytes[$o..($o+15)]; $listSize=[BitConverter]::ToUInt32($Bytes,$o+16); $hdr=[BitConverter]::ToUInt32($Bytes,$o+20); $sz=[BitConverter]::ToUInt32($Bytes,$o+24) + 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 } } + if ($x -and $sz -gt 16) { + $so=$o+28+$hdr; $end=$o+$listSize + while ($so+$sz -le $end) { + $co=$so+16; $cs=[int]$sz-16 + if ($co+$cs -le $Bytes.Length -and $cs -gt 0) { + try { $certs += New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(, [byte[]]($Bytes[$co..($co+$cs-1)])) } catch { } } - $sigOffset += $sigSize + $so += $sz } } - $offset += $sigListSize + $o += $listSize } return $certs } - function Convert-CertToInfo { - param([System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert) - return [ordered]@{ Subject=$Cert.Subject; Thumbprint=$Cert.Thumbprint - NotBefore=$Cert.NotBefore.ToString('yyyy-MM-dd'); NotAfter=$Cert.NotAfter.ToString('yyyy-MM-dd') } + 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 { - $obj = Get-SecureBootUEFI -Name $Name -ErrorAction Stop - return [System.Text.Encoding]::ASCII.GetString($obj.Bytes) - } catch { return $null } + try { return [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI -Name $Name -ErrorAction Stop).Bytes) } catch { return $null } } function Get-CertificateStatus { - $status = [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 + $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 } - - # KEK try { - $kekObj = Get-SecureBootUEFI -Name KEK -ErrorAction Stop - foreach ($cert in (Parse-EFISignatureList -Bytes $kekObj.Bytes)) { - $subj = $cert.Subject; $info = Convert-CertToInfo $cert - if ($subj -like '*KEK CA 2011*') { $status.KEK.Has2011 = $true; $status.KEK.Certs2011 += $info } - if ($subj -like "*$CN_KEK2023*" -or $subj -like '*KEK*CA 2023*') { $status.KEK.Has2023 = $true; $status.KEK.Certs2023 += $info } + foreach ($c in (Parse-EFISignatureList -Bytes (Get-SecureBootUEFI -Name KEK -ErrorAction Stop).Bytes)) { + $s=$c.Subject; $info=Convert-CertToInfo $c + if ($s -like '*KEK CA 2011*') { $st.KEK.Has2011=$true; $st.KEK.Certs2011+=$info } + if ($s -like "*$CN_KEK2023*" -or $s -like '*KEK*CA 2023*') { $st.KEK.Has2023=$true; $st.KEK.Certs2023+=$info } } - } catch { $status.KEK.Error = $_.Exception.Message } - - # DB + } catch { $st.KEK.Error=$_.Exception.Message } try { - $dbObj = Get-SecureBootUEFI -Name db -ErrorAction Stop - foreach ($cert in (Parse-EFISignatureList -Bytes $dbObj.Bytes)) { - $subj = $cert.Subject; $info = Convert-CertToInfo $cert - if ($subj -like '*UEFI CA 2011*') { $status.DB.Has2011UEFI = $true; $status.DB.Certs2011 += $info } - if ($subj -like '*Windows Production PCA 2011*' -or $subj -like '*Windows PCA 2011*') { $status.DB.Has2011WindowsPCA = $true; $status.DB.Certs2011 += $info } - if ($subj -like "*$CN_UEFI2023*" -and $subj -notlike '*Option ROM*' -and $subj -notlike '*Windows UEFI*') { $status.DB.Has2023UEFI = $true; $status.DB.Certs2023 += $info } - if ($subj -like "*$CN_OPTROM2023*") { $status.DB.Has2023OptionROM = $true; $status.DB.Certs2023 += $info } - if ($subj -like "*$CN_WINUEFI2023*") { $status.DB.Has2023WindowsUEFI = $true; $status.DB.Certs2023 += $info } + foreach ($c in (Parse-EFISignatureList -Bytes (Get-SecureBootUEFI -Name db -ErrorAction Stop).Bytes)) { + $s=$c.Subject; $info=Convert-CertToInfo $c + if ($s -like '*UEFI CA 2011*') { $st.DB.Has2011UEFI=$true; $st.DB.Certs2011+=$info } + if ($s -like '*Windows Production PCA 2011*' -or $s -like '*Windows PCA 2011*') { $st.DB.Has2011WindowsPCA=$true; $st.DB.Certs2011+=$info } + if ($s -like "*$CN_UEFI2023*" -and $s -notlike '*Option ROM*' -and $s -notlike '*Windows UEFI*') { $st.DB.Has2023UEFI=$true; $st.DB.Certs2023+=$info } + if ($s -like "*$CN_OPTROM2023*") { $st.DB.Has2023OptionROM=$true; $st.DB.Certs2023+=$info } + if ($s -like "*$CN_WINUEFI2023*") { $st.DB.Has2023WindowsUEFI=$true; $st.DB.Certs2023+=$info } } - } catch { $status.DB.Error = $_.Exception.Message } - - # ASCII fallback (kdyby X.509 parse selhal) — jen doplní booleany, nikdy je neshazuje - $kekAscii = Get-SbVarAscii -Name KEK - if ($kekAscii) { if ($kekAscii -match [regex]::Escape($CN_KEK2023)) { $status.KEK.Has2023 = $true } } - $dbAscii = Get-SbVarAscii -Name db - if ($dbAscii) { - if ($dbAscii -match [regex]::Escape($CN_WINUEFI2023)) { $status.DB.Has2023WindowsUEFI = $true } - if ($dbAscii -match [regex]::Escape($CN_OPTROM2023)) { $status.DB.Has2023OptionROM = $true } - if ($dbAscii -match ('Microsoft UEFI CA 2023')) { $status.DB.Has2023UEFI = $true } + } catch { $st.DB.Error=$_.Exception.Message } + # ASCII fallback (kdyby X.509 parse selhal) — jen doplní booleany + $k=Get-SbVarAscii KEK; if ($k -and $k -match [regex]::Escape($CN_KEK2023)) { $st.KEK.Has2023=$true } + $d=Get-SbVarAscii db + if ($d) { + if ($d -match [regex]::Escape($CN_WINUEFI2023)) { $st.DB.Has2023WindowsUEFI=$true } + if ($d -match [regex]::Escape($CN_OPTROM2023)) { $st.DB.Has2023OptionROM=$true } + if ($d -match 'Microsoft UEFI CA 2023') { $st.DB.Has2023UEFI=$true } } - $dbxAscii = Get-SbVarAscii -Name dbx - if ($dbxAscii -and ($dbxAscii -match [regex]::Escape($CN_PCA2011))) { $status.DbxRevokesPCA2011 = $true } - - $status.AnyExpiring2011 = $status.KEK.Has2011 -or $status.DB.Has2011UEFI -or $status.DB.Has2011WindowsPCA - return $status + $x=Get-SbVarAscii dbx; if ($x -and $x -match [regex]::Escape($CN_PCA2011)) { $st.DbxRevokesPCA2011=$true } + $st.AnyExpiring2011 = $st.KEK.Has2011 -or $st.DB.Has2011UEFI -or $st.DB.Has2011WindowsPCA + return $st } -#endregion - -#region ── Detekce — registry / events / boot manager / task ────────────────── - function Get-RegistryStatus { $reg = [ordered]@{ - SecureBootEnabled=$null; AvailableUpdates=$null; HighConfidenceOptOut=$null - MicrosoftUpdateManagedOptIn=$null + AvailableUpdates=$null; HighConfidenceOptOut=$null; MicrosoftUpdateManagedOptIn=$null ServicingKeyExists=$false; UEFICA2023Status=$null; UEFICA2023StatusText='KeyNotPresent' UEFICA2023Error=$null; UEFICA2023ErrorEvent=$null; WindowsUEFICA2023Capable=$null + WindowsUEFICA2023CapableText='-'; ConfidenceLevel=$null } - $mainProps = Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue - if ($mainProps) { - $reg.SecureBootEnabled = $mainProps.SecureBootEnabled - $reg.AvailableUpdates = $mainProps.AvailableUpdates - $reg.HighConfidenceOptOut = $mainProps.HighConfidenceOptOut - $reg.MicrosoftUpdateManagedOptIn = $mainProps.MicrosoftUpdateManagedOptIn + $m = Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue + if ($m) { + $reg.AvailableUpdates=$m.AvailableUpdates; $reg.HighConfidenceOptOut=$m.HighConfidenceOptOut + $reg.MicrosoftUpdateManagedOptIn=$m.MicrosoftUpdateManagedOptIn } - $svcProps = Get-ItemProperty $REG_SERVICING -ErrorAction SilentlyContinue - if ($svcProps) { - $reg.ServicingKeyExists = $true - $reg.UEFICA2023Status = $svcProps.UEFICA2023Status - $reg.UEFICA2023Error = $svcProps.UEFICA2023Error - $reg.UEFICA2023ErrorEvent = $svcProps.UEFICA2023ErrorEvent - $reg.WindowsUEFICA2023Capable = $svcProps.WindowsUEFICA2023Capable - $reg.UEFICA2023StatusText = switch ($reg.UEFICA2023Status) { - 0 { 'NotStarted' } 1 { 'InProgress' } 2 { 'Success' } 3 { 'Failed' } - $null { 'KeyNotPresent' } default { "Unknown ($($reg.UEFICA2023Status))" } - } + $s = Get-ItemProperty $REG_SERVICING -ErrorAction SilentlyContinue + if ($s) { + $reg.ServicingKeyExists=$true + $reg.UEFICA2023Status=$s.UEFICA2023Status + $reg.UEFICA2023Error=$s.UEFICA2023Error + $reg.UEFICA2023ErrorEvent=$s.UEFICA2023ErrorEvent + $reg.WindowsUEFICA2023Capable=$s.WindowsUEFICA2023Capable + $reg.ConfidenceLevel=$s.ConfidenceLevel } + # UEFICA2023Status je REG_SZ (NotStarted/InProgress/Updated); defenzivně i číselná varianta + $v=$reg.UEFICA2023Status + if ($null -eq $v) { $reg.UEFICA2023StatusText='KeyNotPresent' } + elseif ($v -is [string] -and $v -ne '') { $reg.UEFICA2023StatusText=$v } + else { $reg.UEFICA2023StatusText = switch ([int]$v) { 0{'NotStarted'} 1{'InProgress'} 2{'Updated'} 3{'Failed'} default {"($v)"} } } + # WindowsUEFICA2023Capable: 0/1/2 + $reg.WindowsUEFICA2023CapableText = switch ($reg.WindowsUEFICA2023Capable) { + 0 {'0 = Windows UEFI CA 2023 NENÍ v DB'} 1 {'1 = cert v DB (boot mgr 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 - $note = switch ($iv) { - 0 { 'vše aplikováno' } - 0x4100 { 'KEK/DB hotové — restart pro Boot Manager' } - 0x4000 { 'fáze Boot Manageru' } - 0x5944 { 'naplánována plná sada' } - default { 'zbývá aplikovat' } - } - return "$hex ($note)" + $iv=[int]$v; $hex='0x{0:X}' -f $iv + $n = switch ($iv) { + 0 {'vše hotovo (proces dokončen)'} 0x4000 {'vše aplikováno — čeká na finální restart'} + 0x4100 {'boot manager 2023 nasazen na ESP'} 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 {'zbývá aplikovat'} } + return "$hex ($n)" } function Get-EventLogStatus { - $evt = [ordered]@{ LastEventId=$null; LastEventTime=$null; ConfidenceLevel='NoRelevantEvents' - RelevantEvents=@(); ById=@{}; Error=$null } - $ids = @(1795,1796,1799,1800,1801,1802,1803,1808) + $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) { - $e = $sorted | Where-Object { $_.Id -eq $id } | Select-Object -First 1 - if ($e) { $evt.ById[$id] = [ordered]@{ Time=$e.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss'); Message=($e.Message -replace '\s+',' ').Trim() } } - } - foreach ($e in ($sorted | Select-Object -First 8)) { - $evt.RelevantEvents += [ordered]@{ EventId=$e.Id; TimeCreated=$e.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss'); Level=$e.LevelDisplayName } - } - $last = $sorted | Select-Object -First 1 - $evt.LastEventId = $last.Id - $evt.LastEventTime = $last.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') - $evt.ConfidenceLevel = switch ($last.Id) { - 1808 { 'HighConfidence-Success' } 1799 { 'BootManager-Installed' } 1801 { 'HighConfidence-Failed' } - 1795 { 'Failed-FirmwareError' } 1796 { 'Failed-VariableUpdate' } 1803 { 'Blocked-NoOemPK' } - 1802 { 'Pending' } default { 'Informational' } } + 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')} } } + 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 } } + $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 -eq 'NoMatchingEventsException') { $evt.ConfidenceLevel = 'NoRelevantEvents' } - else { $evt.Error = $_.Exception.Message; $evt.ConfidenceLevel = 'EventLogError' } + if ($_.CategoryInfo.Reason -ne 'NoMatchingEventsException') { $e.Error=$_.Exception.Message } } - return $evt + return $e } function Get-TaskExists { - try { $null = Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction Stop; return $true } - catch { return $false } + try { $null = Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction Stop; return $true } catch { return $false } } function Get-BootManagerStatus { + # Ověří, zda je bootmgfw.efi na ESP podepsaný Windows UEFI CA 2023 (staged stav). param([bool]$SkipFileCheck) - $r = [ordered]@{ HasCA2023=$false; Evidence=@(); Chain=$null; Signer=$null; Error=$null; Checked=$false } - - # Lehký signál: Event 1799 (Boot Manager 2023 nainstalován) - try { - $e = Get-WinEvent -FilterHashtable @{LogName='System';Id=1799} -MaxEvents 1 -ErrorAction Stop | Select-Object -First 1 - if ($e -and ($e.Message -match 'Windows UEFI CA 2023')) { $r.HasCA2023 = $true; $r.Evidence += 'Event1799'; $r.Checked = $true } - } catch { } - + $r=[ordered]@{ EspHas2023=$false; Evidence=@(); Chain=$null; Signer=$null; Error=$null; Checked=$false } if ($SkipFileCheck) { return $r } - - # Robustní: mount ESP (read-only), certutil -dump bootmgfw.efi - $drive = 'S'; $mountedByUs = $false; $tmp = $null + $drive='S'; $mounted=$false; $tmp=$null try { - if (-not (Get-PSDrive -Name $drive -ErrorAction SilentlyContinue)) { - & mountvol "$drive`:" /S 2>$null | Out-Null - Start-Sleep -Seconds 2 - $mountedByUs = $true - } - $src = "$drive`:\EFI\Microsoft\Boot\bootmgfw.efi" + if (-not (Get-PSDrive -Name $drive -ErrorAction SilentlyContinue)) { & mountvol "$drive`:" /S 2>$null | Out-Null; Start-Sleep -Seconds 2; $mounted=$true } + $src="$drive`:\EFI\Microsoft\Boot\bootmgfw.efi" if (Test-Path -LiteralPath $src) { - $r.Checked = $true - $tmp = Join-Path $env:TEMP ("bootmgfw_{0}.efi" -f ([guid]::NewGuid().ToString('N'))) + $r.Checked=$true + $tmp=Join-Path $env:TEMP ("bootmgfw_{0}.efi" -f ([guid]::NewGuid().ToString('N'))) Copy-Item -LiteralPath $src -Destination $tmp -Force -ErrorAction Stop - $certutil = Get-Command certutil.exe -ErrorAction SilentlyContinue - if ($certutil) { + if (Get-Command certutil.exe -ErrorAction SilentlyContinue) { $raw = & certutil.exe -dump $tmp 2>&1 | Out-String - if ($raw -match 'Windows UEFI CA 2023') { - $r.HasCA2023 = $true - if ($r.Evidence -notcontains 'certutil') { $r.Evidence += 'certutil' } - } - $issuer = ([regex]::Match($raw, '(?im)^\s*Issuer:\s*(.+)$')).Groups[1].Value.Trim() - if ($issuer) { $r.Chain = "Issuer: $issuer" } + if ($raw -match 'Windows UEFI CA 2023') { $r.EspHas2023=$true; $r.Evidence+='certutil' } + $iss=([regex]::Match($raw,'(?im)^\s*Issuer:\s*(.+)$')).Groups[1].Value.Trim(); if ($iss){ $r.Chain="Issuer: $iss" } } - try { - $sig = Get-AuthenticodeSignature -FilePath $tmp -ErrorAction Stop - if ($sig.SignerCertificate) { $r.Signer = $sig.SignerCertificate.Subject } - } catch { } - } else { - $r.Error = 'bootmgfw.efi nenalezen na ESP' - } - } catch { - $r.Error = $_.Exception.Message - } finally { + try { $sig=Get-AuthenticodeSignature -FilePath $tmp -ErrorAction Stop; if ($sig.SignerCertificate){ $r.Signer=$sig.SignerCertificate.Subject } } catch { } + } else { $r.Error='bootmgfw.efi nenalezen na ESP' } + } catch { $r.Error=$_.Exception.Message } + finally { if ($tmp -and (Test-Path -LiteralPath $tmp)) { Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue } - if ($mountedByUs) { & mountvol "$drive`:" /D 2>$null | Out-Null } + if ($mounted) { & mountvol "$drive`:" /D 2>$null | Out-Null } } return $r } @@ -460,42 +400,41 @@ function Get-BootManagerStatus { function Build-Phases { param($Sb, $Cert, $Boot, $TaskExists, $Reg, $Evt) + $e1795=$Evt.ById[1795]; $e1796=$Evt.ById[1796] + $cap=$Reg.WindowsUEFICA2023Capable + $bmActive = ($cap -eq 2) -or ($null -ne $Evt.ById[1808]) -or ($null -ne $Evt.ById[1799]) + $bmStaged = [bool]$Boot.EspHas2023 - $e1803 = $Evt.ById[1803]; $e1795 = $Evt.ById[1795]; $e1796 = $Evt.ById[1796] + $ph=[ordered]@{} + function _p($req,$done,$label){ [ordered]@{ Req=$req; Done=[bool]$done; Label=$label; State=$null; Note=$null } } + $ph['SecureBootEnabled'] = _p $true $Sb.IsEnabled 'Secure Boot zapnutý' + $ph['TaskExists'] = _p $true $TaskExists 'Servicing task k dispozici' + $ph['Kek2023'] = _p $true $Cert.KEK.Has2023 "KEK: Microsoft Corporation $CN_KEK2023" + $ph['Db2023Windows'] = _p $true $Cert.DB.Has2023WindowsUEFI "DB: $CN_WINUEFI2023" + $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['Db2023OptionRom'] = _p $false $Cert.DB.Has2023OptionROM "DB: $CN_OPTROM2023 (volitelné)" + $ph['DbxRevoked'] = _p $false $Cert.DbxRevokesPCA2011 'DBX: revokace starého boot manageru 2011 (volitelné)' - $ph = [ordered]@{} - function _p($req,$done,$label) { return [ordered]@{ Req=$req; Done=[bool]$done; Label=$label; State=$null; Note=$null } } - - $ph['SecureBootEnabled'] = _p $true $Sb.IsEnabled 'Secure Boot zapnutý' - $ph['TaskExists'] = _p $true $TaskExists 'Servicing task k dispozici' - $ph['Kek2023'] = _p $true $Cert.KEK.Has2023 "KEK: Microsoft Corporation $CN_KEK2023" - $ph['Db2023Windows'] = _p $true $Cert.DB.Has2023WindowsUEFI "DB: $CN_WINUEFI2023" - $ph['BootManager2023'] = _p $true $Boot.HasCA2023 'Boot Manager podepsaný Windows UEFI CA 2023' - $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['DbxRevoked'] = _p $false $Cert.DbxRevokesPCA2011 'DBX: revokace starého boot manageru 2011 (volitelné)' - - # Stav + poznámky pro checklist $kekDb = $ph['Kek2023'].Done -and $ph['Db2023Windows'].Done + $arrow = [char]0x2190 foreach ($key in $ph.Keys) { - $p = $ph[$key] - if ($p.Done) { $p.State = 'Done'; continue } - if (-not $p.Req) { $p.State = 'Info'; continue } - # nesplněná povinná fáze — rozlišit blokované vs. čekající + $p=$ph[$key] + if ($p.Done) { $p.State='Done'; continue } + if (-not $p.Req) { $p.State='Info'; continue } switch ($key) { 'Kek2023' { - if ($e1803) { $p.State='Fail'; $p.Note='blokováno — chybí OEM PK-signed KEK (Event 1803)' } - elseif ($e1796) { $p.State='Fail'; $p.Note='selhání zápisu proměnné (Event 1796)' } - elseif ($e1795) { $p.State='Fail'; $p.Note='chyba firmwaru (Event 1795)' } - else { $p.State='Pending'; $p.Note='zbývá nasadit' } + if ($e1796) { $p.State='Fail'; $p.Note='selhání zápisu proměnné (Event 1796)' } + elseif ($e1795) { $p.State='Fail'; $p.Note='chyba firmwaru (Event 1795)' } + else { $p.State='Pending'; $p.Note='zbývá nasadit' } } 'BootManager2023' { - if ($kekDb) { $p.State='Pending'; $p.Note="$([char]0x2190) zbývá — vyžaduje RESTART" } - else { $p.State='Pending'; $p.Note='až po nasazení KEK + DB' } + if ($bmStaged) { $p.State='Pending'; $p.Note="$arrow nasazen na ESP — vyžaduje RESTART" } + 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 { - 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' } } } } @@ -504,44 +443,40 @@ function Build-Phases { function Get-RemediationCategory { param($Result) - $sb = $Result.SecureBoot; $reg = $Result.Registry; $evt = $Result.EventLog - $env = $Result.EnvironmentType; $ph = $Result.Phases; $cert = $Result.Certificates + $sb=$Result.SecureBoot; $reg=$Result.Registry; $evt=$Result.EventLog + $env=$Result.EnvironmentType; $ph=$Result.Phases; $cert=$Result.Certificates; $mode=$Result.OperatingMode 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)' } } return @{ Code='NO_SECUREBOOT'; Tag='[X]'; Color='DarkGray'; Label='Secure Boot nepodporováno (Legacy BIOS)' } } if (-not $sb.IsEnabled) { return @{ Code='SECUREBOOT_DISABLED'; Tag='[OFF]'; Color='Yellow'; Label='Secure Boot vypnuto' } } - if (-not $ph['TaskExists'].Done) { - return @{ Code='TASK_MISSING'; Tag='[!]'; Color='Magenta'; Label='Chybí servicing task — nainstalujte aktuální kumulativní update' } + 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)' } } + + $errEvt = $reg.UEFICA2023ErrorEvent -and ([int]$reg.UEFICA2023ErrorEvent -ne 0) + 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)' } } - if ($evt.ById[1803]) { - return @{ Code='KEK_BLOCKED'; Tag='[X]'; Color='Red'; Label='KEK nelze aktualizovat — chybí OEM PK-signed KEK (Event 1803)' } + if ($reg.ConfidenceLevel -like 'Not Supported*') { + return @{ Code='NOT_SUPPORTED'; Tag='[X]'; Color='Red'; Label='Zařízení nepodporuje automatickou aktualizaci (ConfidenceLevel: Not Supported)' } } - if ($reg.UEFICA2023Status -eq 3 -or $evt.ById[1795] -or ($evt.ById[1796] -and -not $ph['Kek2023'].Done) -or $evt.LastEventId -eq 1801) { - return @{ Code='UPDATE_FAILED'; Tag='[FAIL]'; Color='Red'; Label='Selhání aktualizace certifikátů' } + if ($reg.ConfidenceLevel -like 'Temporarily Paused*' -or $evt.ById[1802]) { + return @{ Code='FIRMWARE_UPDATE_NEEDED'; Tag='[FW]'; Color='Magenta'; Label='Aktualizace pozastavena (známý problém) — zkontrolujte firmware u OEM' } } - $reqKeys = @('Kek2023','Db2023Windows','BootManager2023') - $missing = @($reqKeys | 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 + $req=@('Kek2023','Db2023Windows','BootManager2023') + $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 ($cert.AnyExpiring2011) { - return @{ Code='OK_TRANSITION'; Tag="[$SYM_DONE]"; Color='Green'; Label='HOTOVO — nové 2023 certifikáty i Boot Manager nasazeny (staré 2011 ještě přítomné, což je normální)' } - } - return @{ Code='OK'; Tag="[$SYM_DONE]"; Color='Green'; Label='HOTOVO — kompletní 2023 sada i Boot Manager, 2011 odstraněny' } + 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' } } if ($missing.Count -eq 1 -and $missing[0] -eq 'BootManager2023') { - return @{ Code='UPDATE_PENDING'; Tag='[~]'; Color='Yellow'; Label='KEK i DB hotové — zbývá Boot Manager: vyžaduje RESTART' } - } - if ($null -ne $reg.WindowsUEFICA2023Capable -and $reg.WindowsUEFICA2023Capable -eq 0 -and -not $anyApplied) { - return @{ Code='FIRMWARE_UPDATE_NEEDED'; Tag='[FW]'; Color='Magenta'; Label='Firmware nepodporuje nové certifikáty — update u OEM' } - } - if ($anyApplied) { - return @{ Code='UPDATE_PARTIAL'; Tag='[~]'; Color='Cyan'; Label=('Probíhá po částech — zbývá: ' + ($missingLabels -join '; ')) } + return @{ Code='UPDATE_PENDING'; Tag='[~]'; Color='Yellow'; Label='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 '; ')) } } return @{ Code='UPDATE_NEEDED'; Tag='[!]'; Color='Yellow'; Label='Nutná aktualizace certifikátů' } } @@ -551,42 +486,30 @@ function Get-RemediationCategory { function Invoke-Detection { param([bool]$SkipBootMgrFile) - $osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue - $result = [ordered]@{ - AuditTimestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') - Hostname = $env:COMPUTERNAME - OSCaption = if ($osInfo) { $osInfo.Caption } else { $null } - OSBuild = if ($osInfo) { $osInfo.BuildNumber } else { $null } - EnvironmentType = Get-EnvironmentType - Hardware = Get-HardwareInfo - SecureBoot = Get-SecureBootState - Certificates = $null - Registry = Get-RegistryStatus - EventLog = Get-EventLogStatus - TaskExists = Get-TaskExists - BootManager = $null - Phases = $null + $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue + $R = [ordered]@{ + AuditTimestamp=(Get-Date).ToString('yyyy-MM-dd HH:mm:ss'); Hostname=$env:COMPUTERNAME + OSCaption=if($os){$os.Caption}else{$null}; OSBuild=if($os){$os.BuildNumber}else{$null} + 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 ($result.SecureBoot.IsUEFI -and $result.SecureBoot.IsSupported) { - $result.Certificates = Get-CertificateStatus - $result.BootManager = Get-BootManagerStatus -SkipFileCheck:$SkipBootMgrFile + if ($R.SecureBoot.IsUEFI -and $R.SecureBoot.IsSupported) { + $R.OperatingMode = Get-SecureBootMode + $R.Certificates = Get-CertificateStatus + $R.BootManager = Get-BootManagerStatus -SkipFileCheck:$SkipBootMgrFile } else { - $result.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 } - $result.BootManager = [ordered]@{ HasCA2023=$false; Evidence=@(); Chain=$null; Signer=$null; Error='Secure Boot nedostupný'; Checked=$false } + $R.Certificates = [ordered]@{ KEK=[ordered]@{Has2011=$false;Has2023=$false;Certs2011=@();Certs2023=@();Error='SB nedostupný'} + 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 } } - - $result.Phases = Build-Phases -Sb $result.SecureBoot -Cert $result.Certificates -Boot $result.BootManager ` - -TaskExists $result.TaskExists -Reg $result.Registry -Evt $result.EventLog - $cat = Get-RemediationCategory -Result $result - $result.Category = $cat.Code - $result.CategoryLabel = "$($cat.Tag) $($cat.Label)" - $result.CategoryColor = $cat.Color - return $result + $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 + $cat = Get-RemediationCategory -Result $R + $R.Category=$cat.Code; $R.CategoryLabel="$($cat.Tag) $($cat.Label)"; $R.CategoryColor=$cat.Color + return $R } #endregion @@ -595,32 +518,35 @@ function Invoke-Detection { function Show-DetectionSummary { param($R) - $sb = $R.SecureBoot; $c = $R.Certificates; $reg = $R.Registry; $evt = $R.EventLog; $hw = $R.Hardware; $ph = $R.Phases + $sb=$R.SecureBoot; $c=$R.Certificates; $reg=$R.Registry; $evt=$R.EventLog; $hw=$R.Hardware; $ph=$R.Phases; $bl=$R.BitLocker Write-Head 'SERVER' - Write-KV 'Stroj' ("{0}" -f $R.Hostname) 'White' ("· {0} (build {1})" -f $R.OSCaption, $R.OSBuild) - Write-KV 'Prostředí' ("{0}" -f $R.EnvironmentType) 'White' ("· {0} {1}" -f $hw.Manufacturer, $hw.Model) + Write-KV 'Stroj' $R.Hostname 'White' ("· {0} (build {1})" -f $R.OSCaption, $R.OSBuild) + Write-KV 'Prostředí' $R.EnvironmentType 'White' ("· {0} {1}" -f $hw.Manufacturer, $hw.Model) + if ($sb.IsUEFI -and $sb.IsSupported) { + $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!'}) + } + 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') + $note = if ($blOn -and $bl.UsesPcr) { '· PCR/TPM — před restartem ověř recovery key!' } elseif ($blOn) { '· chráněno' } else { '' } + 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'}) + } Write-Head 'POSTUP AKTUALIZACE (checklist)' - if ($sb.IsUEFI -and $sb.IsSupported) { - foreach ($key in $ph.Keys) { - $p = $ph[$key] - $label = $p.Label - if ($p.Done -and $p.State -eq 'Done' -and $key -eq 'Kek2023' -and $c.KEK.Certs2023.Count) { } - Write-Check -State $p.State -Text $label -Note $p.Note - } - } else { - Write-Check -State 'Fail' -Text 'Secure Boot není podporováno / zapnuto' -Note 'remediace certifikátů zde nedává smysl' - } + if ($sb.IsUEFI -and $sb.IsSupported) { foreach ($k in $ph.Keys) { $p=$ph[$k]; Write-Check -State $p.State -Text $p.Label -Note $p.Note } } + else { Write-Check -State 'Fail' -Text 'Secure Boot není podporováno / zapnuto' -Note 'remediace zde nedává smysl' } Write-Head 'STAV REGISTRŮ / FIRMWARE' Write-KV 'AvailableUpdates' (Get-AvailableUpdatesText $reg.AvailableUpdates) 'Cyan' - Write-KV 'UEFICA2023Status' ("{0} ({1})" -f $reg.UEFICA2023Status, $reg.UEFICA2023StatusText) 'White' - if ($null -ne $reg.WindowsUEFICA2023Capable) { Write-KV 'FW 2023-capable' ([string]$reg.WindowsUEFICA2023Capable) $(if($reg.WindowsUEFICA2023Capable -eq 0){'Red'}else{'White'}) } - if ($null -ne $reg.UEFICA2023Error) { Write-KV 'UEFICA2023Error' ('0x{0:X}' -f [int]$reg.UEFICA2023Error) 'Red' } - if ($R.BootManager.Error) { Write-KV 'Boot Manager' 'neověřen souborově' 'Yellow' ("· {0}" -f $R.BootManager.Error) } - elseif ($R.BootManager.Evidence) { Write-KV 'Boot Manager' ('ověřeno ({0})' -f ($R.BootManager.Evidence -join ',')) $(if($R.BootManager.HasCA2023){'Green'}else{'Yellow'}) } - if ($evt.LastEventId) { Write-KV 'Poslední event' ("EventID {0}" -f $evt.LastEventId) $(if($evt.LastEventId -in @(1808,1799)){'Green'}elseif($evt.LastEventId -in @(1795,1796,1801,1803)){'Red'}else{'White'}) ("· {0}" -f $evt.LastEventTime) } + Write-KV 'UEFICA2023Status' $reg.UEFICA2023StatusText $(if($reg.UEFICA2023StatusText -eq 'Updated'){'Green'}elseif($reg.UEFICA2023StatusText -eq 'Failed'){'Red'}else{'White'}) + Write-KV 'WinUEFICA2023Capable' $reg.WindowsUEFICA2023CapableText $(if($reg.WindowsUEFICA2023Capable -eq 2){'Green'}else{'White'}) + if ($reg.ConfidenceLevel) { Write-KV 'ConfidenceLevel' $reg.ConfidenceLevel $(if($reg.ConfidenceLevel -like 'High*'){'Green'}elseif($reg.ConfidenceLevel -like 'Not Supported*'){'Red'}else{'Yellow'}) } + if ($null -ne $reg.UEFICA2023ErrorEvent -and [int]$reg.UEFICA2023ErrorEvent -ne 0) { Write-KV 'UEFICA2023ErrorEvent' ([string]$reg.UEFICA2023ErrorEvent) 'Red' '· Event ID poslední chyby' } + if ($R.BootManager.Error -and -not $SkipBootManagerFileCheck) { Write-KV 'Boot Manager (ESP)' 'neověřeno souborově' 'Yellow' ("· {0}" -f $R.BootManager.Error) } + elseif ($R.BootManager.EspHas2023) { Write-KV 'Boot Manager (ESP)' 'podepsán CA 2023 (staged)' 'Green' } + if ($evt.LastEventId) { Write-KV 'Poslední event' ("EventID {0}" -f $evt.LastEventId) $(if($evt.LastEventId -in @(1808,1799)){'Green'}elseif($evt.LastEventId -in @(1795,1796)){'Red'}else{'White'}) ("· {0}" -f $evt.LastEventTime) } + Write-Host ' cílový stav: AvailableUpdates=0x0 · UEFICA2023Status=Updated · WinUEFICA2023Capable=2' -ForegroundColor DarkGray Write-Host '' Write-Host ' VÝSLEDEK: ' -ForegroundColor Gray -NoNewline @@ -630,20 +556,25 @@ function Show-DetectionSummary { function Show-DetailedDetection { param($R) - $c = $R.Certificates; $evt = $R.EventLog; $bm = $R.BootManager + $c=$R.Certificates; $evt=$R.EventLog; $bm=$R.BootManager; $reg=$R.Registry Write-Head 'PODROBNOSTI' - foreach ($grp in @(@{N='KEK';O=$c.KEK}, @{N='DB';O=$c.DB})) { - foreach ($ci in (@($grp.O.Certs2011) + @($grp.O.Certs2023))) { - Write-Host (" [{0}] {1}" -f $grp.N, $ci.Subject) -ForegroundColor DarkGray + foreach ($g in @(@{N='KEK';O=$c.KEK}, @{N='DB';O=$c.DB})) { + foreach ($ci in (@($g.O.Certs2011)+@($g.O.Certs2023))) { + Write-Host (" [{0}] {1}" -f $g.N, $ci.Subject) -ForegroundColor DarkGray Write-Host (" do {0} | {1}" -f $ci.NotAfter, $ci.Thumbprint) -ForegroundColor DarkGray } } if ($bm.Chain) { Write-Host (" Boot Manager chain: {0}" -f $bm.Chain) -ForegroundColor DarkGray } if ($bm.Signer) { Write-Host (" Boot Manager signer: {0}" -f $bm.Signer) -ForegroundColor DarkGray } + if ($null -ne $reg.AvailableUpdates) { + Write-Host ' AvailableUpdates — naplánované operace (bit):' -ForegroundColor DarkGray + $iv=[int]$reg.AvailableUpdates + foreach ($b in $AU_BITS) { if ($iv -band $b.Bit) { Write-Host (" 0x{0:X4} {1}" -f $b.Bit, $b.Name) -ForegroundColor Yellow } } + } if ($evt.RelevantEvents.Count) { Write-Host ' Poslední události:' -ForegroundColor DarkGray - foreach ($e in ($evt.RelevantEvents | Select-Object -First 6)) { - $ec = if ($e.EventId -in @(1808,1799)) { 'Green' } elseif ($e.EventId -in @(1795,1796,1801,1803)) { 'Red' } else { 'DarkGray' } + foreach ($e in ($evt.RelevantEvents|Select-Object -First 6)) { + $ec=if($e.EventId -in @(1808,1799)){'Green'}elseif($e.EventId -in @(1795,1796)){'Red'}else{'DarkGray'} Write-Host (" [{0}] EventID {1} {2}" -f $e.TimeCreated, $e.EventId, $e.Level) -ForegroundColor $ec } } @@ -651,67 +582,52 @@ function Show-DetailedDetection { #endregion -#region ── Plán remediace ───────────────────────────────────────────────────── +#region ── Plán remediace + souhlas ─────────────────────────────────────────── function Resolve-RemediationPlan { param($R, [bool]$ForceMode) - $plan = [ordered]@{ Applicable=$false; AskUser=$false; Caution=$null; Reason=$null; NextSteps=@() } - + $plan=[ordered]@{ Applicable=$false; AskUser=$false; Caution=$null; Reason=$null; NextSteps=@() } switch -Wildcard ($R.Category) { - 'OK' { - $plan.Reason='Server je kompletně hotový — žádná akce.' - if ($ForceMode) { $plan.Applicable=$true; $plan.AskUser=$true; $plan.Caution='Už hotovo — -Force vynutí opětovné nastavení.' } - } + 'OK' { $plan.Reason='Server je kompletně hotový — žádná akce.'; if ($ForceMode){ $plan.Applicable=$true; $plan.AskUser=$true } } 'OK_TRANSITION' { - $plan.Reason='Nové 2023 certifikáty i Boot Manager jsou nasazeny. Staré 2011 zůstávají (normální, neexpirují náhle). Žádná akce.' - $plan.NextSteps=@('Nepovinné: po čase ověřte revokaci starého boot manageru (DBX).') - if ($ForceMode) { $plan.Applicable=$true; $plan.AskUser=$true } + $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).') + if ($ForceMode){ $plan.Applicable=$true; $plan.AskUser=$true } } 'UPDATE_NEEDED' { $plan.Applicable=$true; $plan.AskUser=$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 (po částech).' - $plan.Caution='Po nastavení a tasku bude potřeba RESTART; cyklus opakujte, dokud nebude HOTOVO.' - } + '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_PENDING' { $plan.Applicable=$true; $plan.AskUser=$true - $plan.Reason='KEK i DB jsou hotové. Boot Manager se přepíše až po RESTARTU.' - $plan.Caution='Doporučeno: nech znovu nastavit registry/task a pak RESTARTUJ — tím se Boot Manager dokončí.' + $plan.Reason='KEK i DB hotové. Boot Manager se aktivuje až po RESTARTU.' + $plan.Caution='Nech znovu nastavit registry/task a pak RESTARTUJ.' $plan.NextSteps=@('Po tomto kroku RESTARTUJTE server.','Po restartu spusťte kontrolu znovu (nebo -RegisterResume).') } 'UPDATE_FAILED' { if ($R.EventLog.ById[1795]) { - $plan.Reason='Selhání s Event 1795 (chyba firmwaru / Hyper-V hostitele). Příčina je mimo tuto VM.' - $plan.NextSteps=@('Aktualizujte firmware / Hyper-V hostitele (KB5085790).','Poté spusťte kontrolu na VM znovu.') - if ($ForceMode) { $plan.Applicable=$true; $plan.AskUser=$true; $plan.Caution='Event 1795 = problém firmwaru/hostitele; -Force jen zopakuje pokus.' } - } else { - $plan.Applicable=$true; $plan.AskUser=$true - $plan.Reason='Předchozí pokus selhal (Event 1801 / status Failed). Lze zopakovat.' - $plan.Caution='Zkontrolujte UEFICA2023Error výše před opakováním.' - } - } - 'KEK_BLOCKED' { - $plan.Reason='KEK aktualizaci nelze vynutit — zařízení nemá OEM PK-signed KEK (Event 1803).' - $plan.NextSteps=@('Kontaktujte OEM / aktualizujte firmware.','Pokud není řešení, dokumentujte jako výjimku.') - if ($ForceMode) { $plan.Applicable=$true; $plan.AskUser=$true; $plan.Caution='Blokováno firmwarem; -Force se pokusí i tak (pravděpodobně selže).' } - } - 'TASK_MISSING' { - $plan.Reason='Servicing task \Microsoft\Windows\PI\Secure-Boot-Update neexistuje.' - $plan.NextSteps=@('Nainstalujte nejnovější kumulativní update Windows — task se vytvoří.','Poté spusťte kontrolu znovu.') + $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.') + if ($ForceMode){ $plan.Applicable=$true; $plan.AskUser=$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.' } } 'FIRMWARE_UPDATE_NEEDED' { - $plan.Reason='WindowsUEFICA2023Capable=0 — firmware nemusí nové certifikáty podporovat.' - $plan.NextSteps=@('Aktualizujte firmware u výrobce (Dell/HP/Lenovo/Supermicro).','Pokud není dostupný, dokumentujte jako výjimku.') - if ($ForceMode) { $plan.Applicable=$true; $plan.AskUser=$true; $plan.Caution='Firmware se jeví nezpůsobilý; -Force se pokusí i tak.' } + $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.') + if ($ForceMode){ $plan.Applicable=$true; $plan.AskUser=$true; $plan.Caution='Pozastaveno firmwarem; -Force se pokusí i tak.' } } - 'SECUREBOOT_DISABLED' { - $plan.Reason='Secure Boot existuje, ale je vypnutý — zapnutí je rozhodnutí mimo rozsah skriptu.' - $plan.NextSteps=@('Zvažte zapnutí Secure Boot v UEFI/nastavení VM (pozor na BitLocker PCR7).') + 'NOT_SUPPORTED' { + $plan.Reason='ConfidenceLevel: Not Supported — zařízení nepodporuje automatickou cestu.' + $plan.NextSteps=@('Ověřte firmware update u OEM.','Pokud není, dokumentujte jako trvalou výjimku.') } - 'NO_SECUREBOOT*' { - $plan.Reason='Secure Boot není podporováno (Legacy BIOS / VM bez vTPM/UEFI).' - $plan.NextSteps=@('Dokumentujte jako výjimku.') + 'SETUP_MODE' { + $plan.Reason='Secure Boot je v Setup Mode (chybí enrolled Platform Key) — aktualizaci nelze dokončit.' + $plan.NextSteps=@('V UEFI/BIOS obnovte výchozí Secure Boot klíče (enroll PK) / přepněte do User Mode.','Poté spusťte kontrolu znovu.') } + 'TASK_MISSING' { + $plan.Reason='Servicing task neexistuje — typicky chybí build z 10/2025 (KB5066835).' + $plan.NextSteps=@('Nainstalujte nejnovější kumulativní update Windows.','Poté spusťte kontrolu znovu.') + } + 'SECUREBOOT_DISABLED' { $plan.Reason='Secure Boot je vypnutý — zapnutí je rozhodnutí mimo rozsah skriptu.'; $plan.NextSteps=@('Zvažte zapnutí Secure Boot (pozor na BitLocker PCR7).') } + 'NO_SECUREBOOT*' { $plan.Reason='Secure Boot není podporováno (Legacy BIOS / VM bez vTPM).'; $plan.NextSteps=@('Dokumentujte jako výjimku.') } default { $plan.Reason='Stav nebylo možné jednoznačně vyhodnotit.' } } return $plan @@ -721,47 +637,33 @@ 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 + 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)$') - } + $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)$') } } #endregion -#region ── Stav napříč restarty (state.json) + RunOnce resume ───────────────── - -function Get-ScriptPath { - if ($PSCommandPath) { return $PSCommandPath } - if ($MyInvocation.MyCommand.Path) { return $MyInvocation.MyCommand.Path } - return $null -} +#region ── Stav napříč restarty + RunOnce ───────────────────────────────────── +function Get-ScriptPath { if ($PSCommandPath) { return $PSCommandPath }; if ($MyInvocation.MyCommand.Path) { return $MyInvocation.MyCommand.Path }; return $null } 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).ToString('o'); Cycle=$Cycle - Category=$R.Category; AvailableUpdates=('0x{0:X}' -f [int]$R.Registry.AvailableUpdates) } | - ConvertTo-Json | Set-Content -LiteralPath $STATE_FILE -Encoding UTF8 + @{ ComputerName=$env:COMPUTERNAME; Timestamp=(Get-Date).ToString('o'); 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 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 } } - function Register-Resume { - $sp = Get-ScriptPath - if (-not $sp) { Write-Line ' (Nelze zjistit cestu skriptu — auto-recheck nenastaven.)' DarkGray; return } - $cmd = 'powershell.exe -NoProfile -ExecutionPolicy Bypass -File "{0}" -CheckOnly' -f $sp + $sp=Get-ScriptPath; if (-not $sp) { Write-Line ' (Nelze zjistit cestu skriptu — auto-recheck nenastaven.)' DarkGray; return } + $cmd='powershell.exe -NoProfile -ExecutionPolicy Bypass -File "{0}" -CheckOnly' -f $sp try { if (-not (Test-Path $REG_RUNONCE)) { New-Item -Path $REG_RUNONCE -Force | Out-Null } New-ItemProperty -Path $REG_RUNONCE -Name 'SecureBootCA2023Recheck' -Value $cmd -PropertyType String -Force | Out-Null @@ -775,80 +677,56 @@ function Register-Resume { function Invoke-Remediation { param([bool]$SkipBootMgrFile) - $outcome = [ordered]@{ Status='Unknown'; Message=''; LogFile=$script:LogFile } - Write-Head 'REMEDIACE' - Add-LogLine 'REMEDIACE START' + $out=[ordered]@{ Status='Unknown'; Message=''; After=$null; LogFile=$script:LogFile } + Write-Head 'REMEDIACE'; Add-LogLine 'REMEDIACE START' - # Krok 1/3 — registry (oficiální trigger) + ověření read-backem Write-Host ' [1/3] Nastavuji registry (MicrosoftUpdateManagedOptIn=1, AvailableUpdates=0x5944) ... ' -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 Set-ItemProperty -Path $REG_SECUREBOOT -Name 'AvailableUpdates' -Value $AVAILABLE_UPDATES_VALUE -Type DWord -Force -ErrorAction Stop - $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 } + $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 (-not (Test-Path $REG_SERVICING)) { New-Item -Path $REG_SERVICING -Force -ErrorAction Stop | Out-Null } - $verify = (Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction Stop).AvailableUpdates - if ($verify -ne $AVAILABLE_UPDATES_VALUE) { throw "Ověření selhalo — AvailableUpdates=$verify" } + $v=(Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction Stop).AvailableUpdates + if ($v -ne $AVAILABLE_UPDATES_VALUE) { throw "Ověření selhalo — AvailableUpdates=$v" } Write-Host 'hotovo' -ForegroundColor Green Add-LogLine 'Krok 1: MicrosoftUpdateManagedOptIn=1, AvailableUpdates=0x5944 zapsáno a ověřeno' - } catch { - Write-Host 'CHYBA' -ForegroundColor Red - Write-Line (" {0}" -f $_.Exception.Message) Red - $outcome.Status='Error'; $outcome.Message="Zápis registry selhal: $($_.Exception.Message)" - return $outcome - } + } 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 } - # Krok 2/3 — task + čekání na ZMĚNU AvailableUpdates (nebo doběh tasku) - if ($SkipScheduledTask) { - Write-Line ' [2/3] Servicing task přeskočen (-SkipScheduledTask) — spustí se sám (cca á 12 h).' DarkGray - } else { + if ($SkipScheduledTask) { Write-Line ' [2/3] Servicing task přeskočen (-SkipScheduledTask).' DarkGray } + else { Write-Host ' [2/3] Spouštím servicing task a čekám na změnu stavu ' -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 ji zpracuje při příštím servisním běhu.' DarkGray - } else { - $initial = [int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates) + $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 ji zpracuje při servisním běhu.' DarkGray } + else { + $init=[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 + Start-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction Stop; Start-Sleep -Seconds 2 + $el=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 - $now = [int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates) - if ($now -ne $initial) { $changed=$true; break } - } while ($state -eq 'Running' -and $elapsed -lt $TASK_TIMEOUT_SEC) - $now = [int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates) - if ($changed) { Write-Host (" hotovo ({0}s, AvailableUpdates -> 0x{1:X})" -f $elapsed, $now) -ForegroundColor Green } - elseif ($elapsed -ge $TASK_TIMEOUT_SEC) { Write-Host (" timeout {0}s (stav {1})" -f $elapsed,$state) -ForegroundColor Yellow } - else { Write-Host (" hotovo ({0}s, stav {1})" -f $elapsed,$state) -ForegroundColor Green } - Add-LogLine ("Krok 2: task stav=$state, elapsed=${elapsed}s, AvailableUpdates=0x{0:X}" -f $now) - } catch { - Write-Host '— nepodařilo se spustit' -ForegroundColor Yellow - Write-Line (" {0}" -f $_.Exception.Message) DarkGray - } + Start-Sleep -Seconds 3; $el+=3; Write-Host '.' -NoNewline -ForegroundColor DarkGray + $state=(Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction SilentlyContinue).State + if ([int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates) -ne $init) { $changed=$true; break } + } while ($state -eq 'Running' -and $el -lt $TASK_TIMEOUT_SEC) + $now=[int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates) + if ($changed) { Write-Host (" hotovo ({0}s, AvailableUpdates -> 0x{1:X})" -f $el,$now) -ForegroundColor Green } + elseif ($el -ge $TASK_TIMEOUT_SEC) { Write-Host (" timeout {0}s (stav {1})" -f $el,$state) -ForegroundColor Yellow } + else { Write-Host (" hotovo ({0}s, stav {1})" -f $el,$state) -ForegroundColor Green } + Add-LogLine ("Krok 2: stav=$state, elapsed=${el}s, AvailableUpdates=0x{0:X}" -f $now) + } catch { Write-Host '— nepodařilo se spustit' -ForegroundColor Yellow; Write-Line (" {0}" -f $_.Exception.Message) DarkGray } } } - # Krok 3/3 — znovu detekce + aktualizovaný checklist Write-Host ' [3/3] Ověřuji nový stav (vč. Boot Manageru) ... ' -NoNewline Start-Sleep -Seconds 2 - $after = Invoke-Detection -SkipBootMgrFile:$SkipBootMgrFile - Write-Host 'hotovo' -ForegroundColor Green - Write-Host '' + $after=Invoke-Detection -SkipBootMgrFile:$SkipBootMgrFile + Write-Host 'hotovo' -ForegroundColor Green; Write-Host '' Write-Line (' AvailableUpdates: {0}' -f (Get-AvailableUpdatesText $after.Registry.AvailableUpdates)) Cyan - foreach ($key in @('Kek2023','Db2023Windows','BootManager2023')) { - $p = $after.Phases[$key] - Write-Check -State $p.State -Text $p.Label -Note $p.Note - } - $outcome.Status = 'Applied' - $outcome.After = $after - $outcome.Message = 'Registry nastavena, task zpracován. Boot Manager se přepíše až po restartu.' + foreach ($k in @('Kek2023','Db2023Windows','BootManager2023')) { $p=$after.Phases[$k]; Write-Check -State $p.State -Text $p.Label -Note $p.Note } + $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' - return $outcome + return $out } #endregion @@ -869,27 +747,19 @@ if (-not $isAdmin) { Write-Host ''; Write-Host ' ! Neběží jako Administrator $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 } -Write-Host '' -Write-Host ' Zjišťuji stav (vč. ověření podpisu Boot Manageru)…' -ForegroundColor DarkGray - +Write-Host ''; Write-Host ' Zjišťuji stav (vč. ověření Boot Manageru)…' -ForegroundColor DarkGray $result = Invoke-Detection -SkipBootMgrFile:$SkipBootManagerFileCheck.IsPresent Show-DetectionSummary -R $result if ($Detailed) { Show-DetailedDetection -R $result } -# CheckOnly — jen detekce + exit kód +if ($result.SecureBoot.IsEnabled -and -not $result.Registry.ServicingKeyExists) { + Write-Host ''; Write-Host ' ! Chybí klíč SecureBoot\Servicing — pravděpodobně build < 10/2025. Nainstalujte KB5066835+.' -ForegroundColor Yellow +} + if ($CheckOnly) { - Write-Host '' - Write-Rule + Write-Host ''; Write-Rule $code = switch -Wildcard ($result.Category) { - 'OK*' { 0 } - 'KEK_BLOCKED' { 2 } - 'FIRMWARE_UPDATE_NEEDED' { 2 } - 'TASK_MISSING' { 2 } - 'NO_SECUREBOOT*' { 2 } - 'SECUREBOOT_DISABLED' { 2 } - 'UPDATE_FAILED' { 2 } - default { 1 } - } + 'OK*' { 0 } 'UPDATE_NEEDED' { 1 } 'UPDATE_PARTIAL' { 1 } 'UPDATE_PENDING' { 1 } default { 2 } } Write-Host (" CHECK: {0} (exit {1})" -f $result.CategoryLabel, $code) -ForegroundColor $result.CategoryColor if ($PassThru) { $result } exit $code @@ -901,65 +771,48 @@ if ($plan.Reason) { Write-Host (" {0}" -f $plan.Reason) -ForegroundColor Gray } $remediation = $null 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++ } - } elseif ($result.Category -like 'OK*') { - Write-Host ' Není potřeba žádná akce.' -ForegroundColor Green - Clear-ResumeState - } + 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; Clear-ResumeState } } else { 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 - } elseif ($isWhatIf) { + if (-not $isAdmin) { Write-Host ''; Write-Host ' Remediaci nelze provést bez práv Administrator.' -ForegroundColor Yellow } + elseif ($isWhatIf) { Write-Host ''; Write-Host ' -WhatIf — co by remediace udělala (bez změn):' -ForegroundColor Cyan Write-Host (" - MicrosoftUpdateManagedOptIn=1, AvailableUpdates=0x{0:X4}, HighConfidenceOptOut=0" -f $AVAILABLE_UPDATES_VALUE) -ForegroundColor Gray - if (-not $SkipScheduledTask) { Write-Host (" - spustila by servicing task a počkala na změnu stavu") -ForegroundColor Gray } + if (-not $SkipScheduledTask) { Write-Host ' - spustila by servicing task a počkala na změnu stavu' -ForegroundColor Gray } Write-Host ' - server by NErestartovala' -ForegroundColor Gray } else { - $q = 'Chcete nyní zahájit / pokračovat v aktualizaci Secure Boot certifikátů?' - $d = 'Nastaví registry (KB5068202) a spustí servicing task. Server NEBUDE restartován.' + $q='Chcete nyní zahájit / pokračovat v aktualizaci Secure Boot certifikátů?' + $d='Nastaví registry (KB5068202) a spustí servicing task. Server NEBUDE restartován.' if (Get-UserConsent -Question $q -Detail $d) { - $script:LogActive = $true + $script:LogActive=$true $cycle = if ($prev) { [int]$prev.Cycle + 1 } else { 1 } Add-LogLine ("Cyklus #{0} | {1} | kategorie={2}" -f $cycle, $result.Hostname, $result.Category) $remediation = Invoke-Remediation -SkipBootMgrFile:$SkipBootManagerFileCheck.IsPresent if ($remediation.After) { Save-ResumeState -R $remediation.After -Cycle $cycle } - } else { - Write-Host ''; Write-Host ' Remediace neprovedena (volba uživatele).' -ForegroundColor Yellow - } + } else { Write-Host ''; Write-Host ' Remediace neprovedena (volba uživatele).' -ForegroundColor Yellow } } } -# Závěr -Write-Host '' -Write-Rule 'Cyan' +Write-Host ''; Write-Rule 'Cyan' if ($remediation -and $remediation.Status -eq 'Applied') { - $after = $remediation.After - if ($after.Category -like 'OK*') { - Write-Host (" {0} HOTOVO — server je kompletní." -f $SYM_DONE) -ForegroundColor Green - Clear-ResumeState - } else { + $after=$remediation.After + if ($after.Category -like 'OK*') { Write-Host (" {0} HOTOVO — server je kompletní." -f $SYM_DONE) -ForegroundColor Green; Clear-ResumeState } + else { 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 ' 2. BitLocker s PCR7: před restartem ověřte recovery key.' -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')) { + 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 } Write-Host ' 3. Po restartu spusťte kontrolu znovu — dokud nebude zelené HOTOVO.' -ForegroundColor Gray - Write-Host (' AvailableUpdates musí klesat (0x5944 -> 0x4100 -> 0x4000 -> 0x0).') -ForegroundColor DarkGray - if ($RegisterResume) { Register-Resume } - else { Write-Host ' (Tip: -RegisterResume spustí kontrolu po příštím restartu automaticky.)' -ForegroundColor DarkGray } + Write-Host ' Cíl: AvailableUpdates=0x0 · UEFICA2023Status=Updated · WinUEFICA2023Capable=2.' -ForegroundColor DarkGray + if ($RegisterResume) { Register-Resume } else { Write-Host ' (Tip: -RegisterResume spustí kontrolu po příštím restartu automaticky.)' -ForegroundColor DarkGray } } - Write-Host '' - Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray -} elseif ($remediation -and $remediation.Status -eq 'Error') { - Write-Host (' CHYBA REMEDIACE — {0}' -f $remediation.Message) -ForegroundColor Red -} else { - Write-Host ' KONEC — bez změn na serveru.' -ForegroundColor Cyan -} -Write-Rule 'Cyan' -Write-Host '' + Write-Host ''; Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray +} elseif ($remediation -and $remediation.Status -eq 'Error') { Write-Host (' CHYBA REMEDIACE — {0}' -f $remediation.Message) -ForegroundColor Red } +else { Write-Host ' KONEC — bez změn na serveru.' -ForegroundColor Cyan } +Write-Rule 'Cyan'; Write-Host '' if ($PassThru) { return [pscustomobject]@{ Detection=$result; Plan=$plan; Remediation=$remediation } } diff --git a/README.md b/README.md index c5f8cfc..4441d7e 100644 --- a/README.md +++ b/README.md @@ -1,310 +1,190 @@ -# Secure Boot Certificate Remediation - -Sada PowerShell skriptů pro audit a remediari expirujících Secure Boot certifikátů na Windows Serverech (fyzické servery, Hyper-V VM, VMware VM). - ---- - -## 1. Co řešíme - -### Problém - -Microsoft oznámil prostřednictvím [KB5062710](https://support.microsoft.com/en-us/topic/windows-secure-boot-certificate-expiration-and-ca-updates-7ff40d33-95dc-4c3c-8725-a9b95457578e), že původní Secure Boot certifikáty z roku 2011 expirují v průběhu roku 2026. Tyto certifikáty jsou uloženy přímo ve firmware serveru (UEFI KEK a DB databázích) a zajišťují důvěryhodnost celého boot procesu. - -### Expirující certifikáty a termíny - -| Certifikát | Umístění | Expirace | Náhrada | -|---|---|---|---| -| Microsoft Corporation KEK CA 2011 | KEK | **24. června 2026** | Microsoft Corporation KEK 2K CA 2023 | -| Microsoft UEFI CA 2011 | DB | **27. června 2026** | Microsoft UEFI CA 2023 + Microsoft Option ROM UEFI CA 2023 | -| Microsoft Windows Production PCA 2011 | DB | 19. října 2026 | Windows UEFI CA 2023 | - -> **Urgentní:** První dvě expirace jsou 24. a 27. června 2026 — audit je nutné provést okamžitě. - -### Dopad pokud se nic neudělá - -Servery bez nových certifikátů budou **nadále normálně bootovat** — certifikáty sice expirují, ale UEFI firmware je při bootu nevyhodnocuje jako neplatné. Problém nastane postupně: - -- Server přestane být schopen přijímat nové aktualizace Secure Boot databází a revokačních seznamů (DBX) -- Nelze aplikovat mitigace nově objevených boot-level zranitelností -- Mohou být ovlivněny scénáře závislé na Secure Boot důvěře: BitLocker hardening, third-party bootloadery, attestation -- Při případném resetu UEFI na factory defaults nebo reinstalaci firmware může boot selhat - -### Které servery jsou ohroženy - -Ohroženy jsou servery, které splňují **všechny** následující podmínky: - -- Mají UEFI firmware (ne Legacy BIOS) -- Mají zapnutý Secure Boot -- Ještě neobdržely nové 2023 certifikáty (typicky přes Windows Update nebo manuální rollout) - -Servery s Legacy BIOS nebo vypnutým Secure Boot nejsou přímo ohroženy, ale je vhodné je zdokumentovat. - ---- - -## 2. Detekce — jak zjistit aktuální stav - -### Skripty - -| Skript | Účel | -|---|---| -| `Invoke-SecureBootAudit.ps1` | Audit jednoho serveru — vrací JSON + human-readable souhrn | -| `Start-SecureBootFleetAudit.ps1` | Hromadný audit celé sítě — vstup CSV, výstup agregovaný CSV/JSON | - -### Spuštění auditu - -**Lokálně na serveru** (spustit jako Administrator): - -```powershell -.\Invoke-SecureBootAudit.ps1 -``` - -**Vzdáleně z workstation:** - -```powershell -Invoke-Command -ComputerName SERVER01 -FilePath .\Invoke-SecureBootAudit.ps1 -ArgumentList $null, $false, $true -``` - -**Hromadně ze seznamu serverů (CSV soubor se sloupcem `ServerName`):** - -```powershell -.\Start-SecureBootFleetAudit.ps1 -ServerListPath .\servers.csv -OutputPath .\audit_results\ -``` - -**Hromadně přímo ze seznamu:** - -```powershell -.\Start-SecureBootFleetAudit.ps1 -ServerName SERVER01,SERVER02,SERVER03 -``` - -### Výsledné kategorie - -Každý server je zařazen do jedné z těchto kategorií: - -| Kategorie | Popis | Doporučená akce | -|---|---|---| -| ✅ **OK** | Má nové 2023 certifikáty, Secure Boot aktivní | Žádná akce | -| ✅ **OK_TRANSITION** | Má 2023 i stará 2011 certifikáty (přechodný stav) | Žádná akce, monitorovat | -| ⚠️ **UPDATE_NEEDED** | Má pouze 2011 certifikáty, Secure Boot aktivní | Naplánovat rollout | -| ⏳ **UPDATE_PENDING** | Registry nastaveny, čeká na restart | Restartovat server | -| 🔧 **FIRMWARE_UPDATE_NEEDED** | `WindowsUEFICA2023Capable=0` — firmware nepodporuje nové certifikáty | Update firmware u OEM | -| ⏸️ **SECUREBOOT_DISABLED** | Secure Boot existuje v UEFI, ale není zapnuté | Rozhodnutí o enablementu | -| ❌ **NO_SECUREBOOT** | Legacy BIOS — Secure Boot vůbec neexistuje | Dokumentovat jako výjimku | -| ❌ **NO_SECUREBOOT_VM** | VM bez Secure Boot (Gen1 / bez vTPM / BIOS mode) | Dokumentovat jako výjimku | -| 🔴 **UPDATE_FAILED** | Selhání aktualizace (viz EventID níže) | Troubleshooting | - -### Registry hodnoty a jejich výklad - -Klíč `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing`: - -| Hodnota | Typ | Výklad | -|---|---|---| -| `UEFICA2023Status = 0` | NotStarted | Aktualizace ještě nebyla spuštěna | -| `UEFICA2023Status = 1` | InProgress | Aktualizace probíhá | -| `UEFICA2023Status = 2` | Success | Aktualizace proběhla, čeká se na restart | -| `UEFICA2023Status = 3` | Failed | Aktualizace selhala — viz `UEFICA2023Error` | -| `WindowsUEFICA2023Capable = 0` | — | Firmware nepodporuje nové certifikáty (potřeba update OEM firmware) | -| `UEFICA2023Error` | HRESULT kód | Chybový kód při selhání | - -Klíč `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot`: - -| Hodnota | Výklad | -|---|---| -| `AvailableUpdates` | Bitmaska dostupných aktualizací; 0x5944 = plná sada (nastavíme při remediaci) | -| `HighConfidenceOptOut = 1` | Server se odhlásil z high-confidence update path — **zablokuje aktualizaci** | - -### EventID v System logu - -| EventID | Zdroj | Typ | Výklad | -|---|---|---|---| -| **1808** | Microsoft-Windows-Eventlog | Information | ✅ Certifikáty úspěšně aplikovány — aktualizace proběhla v pořádku | -| **1801** | Microsoft-Windows-Eventlog | Error | ❌ Certifikáty NEBYLY aplikovány — selhání high-confidence update | -| **1795** | Microsoft-Windows-Eventlog | Error | ❌ Selhání aktualizace na Hyper-V VM (viz níže) | -| **1796** | Microsoft-Windows-Eventlog | Information | Průběhová informace | -| **1800** | Microsoft-Windows-Eventlog | Information | Průběhová informace | -| **1802** | Microsoft-Windows-Eventlog | Information | Aktualizace čeká na restart | -| **1803** | Microsoft-Windows-Eventlog | Information | Aktualizace probíhá | - -Rychlé ověření z PowerShell: - -```powershell -Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = @(1795,1801,1808) } -MaxEvents 10 | - Select-Object TimeCreated, Id, Message | Format-List -``` - -### Specifika prostředí - -#### Hyper-V VM - -- Virtuální stroje **Generation 1** (BIOS mode) nemají Secure Boot — kategorie `NO_SECUREBOOT_VM`, není co řešit. -- Virtuální stroje **Generation 2** mají virtualizovaný UEFI s Secure Boot, ale aktualizace certifikátů závisí na verzi hypervizoru. -- **Známý problém (Event 1795):** Na starších hostitelích selhávala aktualizace certifikátů ve VM s EventID 1795. Microsoft vydal opravu v březnu 2026 (KB5085790). Postup: - 1. Ověřit verzi Windows Server na Hyper-V hostiteli - 2. Aplikovat příslušné kumulativní aktualizace na hostitele - 3. Spustit audit znovu na VM - -#### VMware ESXi - -- Secure Boot ve VMware VM závisí na konfiguraci VM hardware (EFI firmware + Secure Boot zaškrtnuto v nastavení VM). -- VMware **nepředává** nové certifikáty automaticky — aktualizace musí proběhnout přes Windows stejně jako na fyzickém stroji. -- Kategorie `NO_SECUREBOOT_VM` u VMware VM = VM nemá Secure Boot vůbec povoleno. - -#### Fyzické servery s Legacy BIOS - -- Kategorie `NO_SECUREBOOT` — Secure Boot není podporováno. Firmware je třeba překonfigurovat (boot mode UEFI) nebo server dokumentovat jako výjimku. - -#### Starší firmware - -- Servery s firmwarem z doby před cca 2018 mohou vyžadovat update firmware od výrobce (Dell, HP, Lenovo, Supermicro) před tím, než lze nové certifikáty aplikovat. -- Poznávacím znamením je `WindowsUEFICA2023Capable = 0` nebo hodnota `UEFICA2023Error`. -- Servery end-of-life bez dostupného firmware update je nutné dokumentovat jako trvalou výjimku. - ---- - -## 3. Remediace — odstranění problému - -### Skript - -``` -Set-SecureBootCertificateUpdate.ps1 -``` - -Metoda: Registry key dle **KB5068202** — nastaví `AvailableUpdates = 0x5944`, čímž Windows signalizuje, že má aplikovat kompletní sadu nových certifikátů (KEK + UEFI CA + Windows UEFI CA + Boot Manager). - -### Doporučený postup rollout (staging) - -**Fáze 1 — Pilotní test (1–2 servery každého typu)** - -```powershell -# Nejdřív WhatIf — ověřit co se stane bez provedení změn -.\Set-SecureBootCertificateUpdate.ps1 -ServerName PILOT-SERVER01 -WhatIf - -# Aplikovat na pilotní server -.\Set-SecureBootCertificateUpdate.ps1 -ServerName PILOT-SERVER01 - -# Restartovat pilotní server -Restart-Computer -ComputerName PILOT-SERVER01 -Force -``` - -**Fáze 2 — Ověření pilotu (po restartu)** - -```powershell -# Zkontrolovat EventID 1808 (úspěch) nebo 1801 (chyba) -Invoke-Command -ComputerName PILOT-SERVER01 -ScriptBlock { - Get-WinEvent -FilterHashtable @{ LogName='System'; Id=@(1801,1808) } -MaxEvents 5 | - Select-Object TimeCreated, Id, LevelDisplayName, Message | Format-List -} - -# Spustit plný audit pro ověření kategorie OK -Invoke-Command -ComputerName PILOT-SERVER01 -FilePath .\Invoke-SecureBootAudit.ps1 -ArgumentList $null, $false, $true -``` - -**Fáze 3 — Rozšíření na další servery** - -```powershell -# Hromadně ze CSV (sloupec ServerName) -.\Set-SecureBootCertificateUpdate.ps1 -ServerName (Import-Csv .\servery-wave1.csv).ServerName - -# Po restartech ověřit hromadně -.\Start-SecureBootFleetAudit.ps1 -ServerListPath .\servery-wave1.csv -OutputPath .\audit_post\ -``` - -### Co se děje po spuštění skriptu - -1. Skript nastaví `AvailableUpdates = 0x5944` v registry a resetuje `HighConfidenceOptOut = 0` -2. Spustí scheduled task `\Microsoft\Windows\PI\Secure-Boot-Update` (pokud existuje; jinak se task spustí sám do 12 hodin) -3. Task zpracuje registry a připraví certifikáty — status přejde z `NotStarted` → `InProgress` → `Success` -4. **Certifikáty se aplikují až při příštím restartu** — Boot Manager zapíše nové certifikáty do UEFI firmware -5. Po restartu se v System logu objeví EventID **1808** (úspěch) nebo **1801** (chyba) - -### Dopad na server - -| Aspekt | Detail | -|---|---| -| **Dostupnost** | Žádný výpadek při nastavení registry. Restart je standardní plánovaný restart. | -| **Riziko selhání bootu** | **Minimální** — neúspěšná aktualizace certifikátů neohrozí boot. Server nastartuje i se starými certifikáty. | -| **Počet restartů** | Obvykle 1 restart. Ve výjimečných případech může být vyžadován druhý. | -| **Čas po restartu** | Certifikáty jsou aplikovány v rané fázi bootu — uživatelé to nepocítí. | -| **BitLocker** | Pokud je BitLocker aktivní s PCR7 (Secure Boot measurement), ověřit recovery key před restartem. | -| **Monitoring** | Po restartu čekat min. 48 hodin než prohlásíme server za stabilní. | - -> **Doporučení pro BitLocker:** Před restartem na serverech s BitLockerem ověřit dostupnost recovery key: `manage-bde -protectors -get C:` - -### Ověření úspěchu - -**1. Event log — primární ověření:** - -```powershell -Get-WinEvent -FilterHashtable @{ LogName='System'; Id=1808 } -MaxEvents 3 | - Select-Object TimeCreated, Message -``` - -EventID **1808** potvrzuje úspěšnou aplikaci certifikátů. - -**2. Audit skript — kompletní ověření:** - -```powershell -.\Invoke-SecureBootAudit.ps1 -# Očekávaný výstup: kategorie "✅ OK" nebo "✅ OK_TRANSITION" -``` - -**3. Ruční kontrola přítomnosti certifikátů:** - -```powershell -# KEK databáze — hledat 2023 certifikáty -$kek = Get-SecureBootUEFI KEK -# DB databáze -$db = Get-SecureBootUEFI db -``` - -**4. Registry stav:** - -```powershell -Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing' | - Select-Object UEFICA2023Status, UEFICA2023Error, WindowsUEFICA2023Capable -# UEFICA2023Status = 2 znamená Success -``` - -### Troubleshooting časté chyby - -**Event 1801 — high-confidence update selhal:** -```powershell -# Zkontrolovat UEFICA2023Error -(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing').UEFICA2023Error - -# Zkusit znovu s -Force -.\Set-SecureBootCertificateUpdate.ps1 -ServerName -Force -``` - -**Event 1795 — Hyper-V VM selhání:** -- Zkontrolovat verzi Windows Server hostitele a aplikovat kumulativní update (KB5085790) -- Po aktualizaci hostitele spustit remediaci na VM znovu - -**WindowsUEFICA2023Capable = 0 — firmware nepodporuje:** -- Zkontrolovat dostupnost firmware update u výrobce serveru -- Dell: Dell Update Package / iDRAC -- HP: HP Service Pack for ProLiant (SPP) -- Lenovo: Lenovo XClarity -- Supermicro: BIOS update přes IPMI -- Pokud firmware update není dostupný → dokumentovat jako trvalou výjimku - -**HighConfidenceOptOut = 1 — server se odhlásil:** -```powershell -# Resetovat OptOut -Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot' ` - -Name HighConfidenceOptOut -Value 0 -Type DWord -# Pak spustit remediaci znovu -``` - ---- - -## Reference - -| KB | Popis | -|---|---| -| [KB5062710](https://support.microsoft.com/en-us/topic/windows-secure-boot-certificate-expiration-and-ca-updates-7ff40d33-95dc-4c3c-8725-a9b95457578e) | Přehled: expirace certifikátů a CA aktualizace | -| [KB5062713](https://support.microsoft.com/en-us/help/5062713) | IT Pro guidance | -| [KB5068202](https://support.microsoft.com/en-us/help/5068202) | Metoda registry klíčů (použita v tomto projektu) | -| [KB5068198](https://support.microsoft.com/en-us/help/5068198) | Metoda Group Policy | -| [KB5068197](https://support.microsoft.com/en-us/help/5068197) | Metoda WinCS CLI | -| [KB5085046](https://support.microsoft.com/en-us/help/5085046) | Troubleshooting guide | -| [KB5085790](https://support.microsoft.com/en-us/help/5085790) | Known issues a resolutions (vč. Hyper-V fix) | +# Secure Boot CA 2023 — kontrola a remediace + +Jediný PowerShell skript **`Invoke-SecureBootRemediation.ps1`** pro audit a řízenou remediaci expirujících Secure Boot certifikátů na Windows Serverech (fyzické stroje, Hyper-V VM, VMware VM). + +--- + +## 1. Co řešíme + +Microsoft v upozornění [KB5062710](https://support.microsoft.com/en-us/topic/windows-secure-boot-certificate-expiration-and-ca-updates-7ff40d33-95dc-4c3c-8725-a9b95457578e) oznámil, že původní Secure Boot certifikáty z roku **2011** expirují v průběhu roku **2026**. Tyto certifikáty jsou uloženy přímo ve firmware (UEFI KEK a DB databázích) a zajišťují důvěryhodnost celého boot procesu. + +| Certifikát | Umístění | Expirace | Náhrada 2023 | +|---|---|---|---| +| Microsoft Corporation KEK CA 2011 | KEK | **24. 6. 2026** | Microsoft Corporation KEK 2K CA 2023 | +| Microsoft UEFI CA 2011 | DB | **27. 6. 2026** | Microsoft UEFI CA 2023 + Option ROM UEFI CA 2023 | +| Microsoft Windows Production PCA 2011 | DB | 19. 10. 2026 | Windows UEFI CA 2023 | + +**Dopad, pokud se nic neudělá:** server bude dál bootovat, ale přestane přijímat nové ochrany boot procesu — aktualizace Boot Manageru, revokace (DBX) a mitigace nově objevených boot-level zranitelností. Postupně se sníží ochrana a ovlivní to scénáře závislé na Secure Boot důvěře (BitLocker hardening, third-party bootloadery). + +**Pozor — co je skutečné „hotovo":** nestačí jen mít nové certifikáty v KEK/DB. Aktualizace je dokončená, až když se systém **reálně bootuje z Boot Manageru podepsaného Windows UEFI CA 2023** (signalizuje `WindowsUEFICA2023Capable = 2` / Event 1808). Tento poslední krok se aplikuje **až po restartu**. Skript proto kontroluje i Boot Manager, ne jen certifikáty. + +--- + +## 2. Jak skript správně použít + +### Požadavky + +- **Windows PowerShell 5.1+**, spuštěno jako **Administrator** +- Build Windows z **14. 10. 2025 nebo novější** ([KB5066835](https://support.microsoft.com/en-us/topic/october-14-2025-kb5066835-os-builds-26200-6899-and-26100-6899-1db237d8-9f3b-4218-9515-3e0a32729685)) — bez něj neexistují servicing registry klíče ani úloha. Skript na to upozorní. +- Žádné externí moduly. UEFI databáze čte nativně. + +### Skript NIKDY nerestartuje server + +Restart je nutný k dokončení aktualizace, ale necháváme ho **na vás** (plánované okno, ohled na BitLocker). Skript jen jasně řekne, kdy je restart potřeba. + +### Typický průběh (interaktivně) + +```powershell +# 1) Spustit na serveru jako Administrator +.\Invoke-SecureBootRemediation.ps1 +``` + +Skript provede detekci, ukáže **checklist** a u stavů, kde to dává smysl, se **zeptá**, zda remediaci aplikovat. Po souhlasu nastaví registry a spustí servicing úlohu (počká na ni). Pak: + +```text +2) Naplánujte RESTART serveru +3) Po restartu spusťte skript znovu +4) Opakujte, dokud checklist nebude celý zelený (HOTOVO) +``` + +Aktualizace se aplikuje **po částech na více restartů** — hodnota `AvailableUpdates` postupně klesá `0x5944 → 0x5904 → 0x5104 → 0x4104 → 0x4100 → 0x4000 → 0x0`. Cílový stav je `AvailableUpdates=0x0`, `UEFICA2023Status=Updated`, `WindowsUEFICA2023Capable=2`. + +### Důležité před restartem + +Pokud má server **BitLocker s ochranou vázanou na TPM/PCR7**, změna Boot Manageru může vyvolat recovery prompt. Skript BitLocker stav zobrazí a zvýrazní — **před restartem ověřte recovery key**. + +### Parametry + +| Parametr | Účel | +|---|---| +| *(bez parametru)* | Interaktivní detekce → dotaz → remediace (bez restartu) | +| `-CheckOnly` | Jen detekce + checklist + **exit kód** (0 = hotovo, 1 = nutná akce, 2 = blokováno). Bez změn a bez dotazu. Vhodné pro RMM/monitoring. | +| `-WhatIf` | Ukáže, co by remediace udělala, bez provedení změn | +| `-AssumeYes` | Neinteraktivně aplikuje remediaci (pokud je smysluplná). Stále bez restartu. | +| `-Force` | Aplikuje i ve stavech, kdy to skript jinak nedoporučuje | +| `-SkipScheduledTask` | Nastaví registry, ale nespustí úlohu (spustí se sama, cca á 12 h) | +| `-SkipBootManagerFileCheck` | Vynechá mount ESP + `certutil`. Dokončení se i tak pozná z `WindowsUEFICA2023Capable=2` / Event 1808 / 1799. | +| `-RegisterResume` | Po remediaci vyžadující restart nastaví `RunOnce`, který po PŘÍŠTÍM (ručním) restartu sám spustí `-CheckOnly`. Nikdy nerestartuje. | +| `-Detailed` | Rozšířený rozpis (certifikáty, události, Boot Manager chain, bit-rozklad `AvailableUpdates`) | +| `-PassThru` | Vrátí výsledný objekt do pipeline | +| `-LogPath ` | Vlastní cesta k logu (jinak log vzniká vedle skriptu jen při reálné remediaci) | + +### Příklady + +```powershell +# Jen kontrola stavu (pro skript/monitoring), bez zásahu +.\Invoke-SecureBootRemediation.ps1 -CheckOnly + +# Náhled co by se stalo, bez změn +.\Invoke-SecureBootRemediation.ps1 -WhatIf + +# Neinteraktivní aplikace + auto-kontrola po příštím restartu +.\Invoke-SecureBootRemediation.ps1 -AssumeYes -RegisterResume + +# Plný rozpis pro diagnostiku +.\Invoke-SecureBootRemediation.ps1 -CheckOnly -Detailed +``` + +--- + +## 3. Co skript dělá (detailně) + +### Fázový checklist + +Server je „HOTOVO" teprve když projdou všechny **povinné** fáze. Volitelné fáze jsou jen informativní. + +| Fáze | Povinná | Splněno když | +|---|---|---| +| Secure Boot zapnutý | ✅ | `Confirm-SecureBootUEFI` = True | +| Servicing task k dispozici | ✅ | existuje `\Microsoft\Windows\PI\Secure-Boot-Update` | +| KEK: Microsoft Corporation KEK 2K CA 2023 | ✅ | cert v KEK databázi | +| DB: Windows UEFI CA 2023 | ✅ | cert v DB databázi | +| Boot Manager aktivní (2023) | ✅ | `WindowsUEFICA2023Capable=2` / Event 1808 / 1799 | +| DB: Microsoft UEFI CA 2023 (3rd-party) | — | volitelné (pro option ROM / non-Windows boot) | +| DB: Option ROM UEFI CA 2023 | — | volitelné | +| DBX: revokace starého boot manageru 2011 | — | volitelné (finální hardening) | + +V checklistu: `[✓]` splněno (zeleně), `[ ]` zbývá, `[✗]` chyba/blokováno. Zvýrazní se aktuální krok („← vyžaduje RESTART"). + +### Detekce — odkud čte + +- **Prostředí a HW** (WMI/CIM): fyzický / Hyper-V VM / VMware VM, výrobce, model, BIOS verze a datum +- **Secure Boot stav** a **operating mode** (User / Deployed / **Setup** / Audit) ze standardních UEFI proměnných +- **Certifikáty** v KEK / DB / DBX — primárně parsováním `EFI_SIGNATURE_LIST` na X.509 (subject, thumbprint, platnost), s ASCII fallbackem +- **Boot Manager** `bootmgfw.efi` na ESP — read-only mount + `certutil -dump` (staged podpis); aktivní stav z registru/eventů +- **Registry** `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot[\Servicing]`: + `AvailableUpdates`, `MicrosoftUpdateManagedOptIn`, `HighConfidenceOptOut`, `UEFICA2023Status` (REG_SZ: NotStarted/InProgress/**Updated**), `UEFICA2023Error`, `UEFICA2023ErrorEvent`, `WindowsUEFICA2023Capable` (0/1/**2**), `ConfidenceLevel` +- **BitLocker** systémové jednotky: stav ochrany a typ protektoru (zvýraznění TPM/PCR7 rizika) +- **Event Log** (System, zdroj TPM-WMI): 1795, 1796, 1799, 1800, 1801, 1802, 1803, 1808 + +### Vyhodnocení — kategorie + +| Kategorie | Význam | Akce | +|---|---|---| +| **OK** | kompletní 2023 sada, aktivní Boot Manager, 2011 odstraněny | žádná | +| **OK_TRANSITION** | 2023 i aktivní Boot Manager hotové, 2011 ještě přítomné (normální) | žádná, monitorovat | +| **UPDATE_NEEDED** | nic 2023 nenasazeno | zahájit remediaci | +| **UPDATE_PARTIAL** | část nasazena, chybí KEK/DB | pokračovat v cyklu | +| **UPDATE_PENDING** | KEK i DB hotové, zbývá aktivovat Boot Manager | **RESTART**, pak znovu zkontrolovat | +| **UPDATE_FAILED** | Event 1795 / `UEFICA2023ErrorEvent` / status Failed | firmware/OEM, troubleshooting | +| **FIRMWARE_UPDATE_NEEDED** | ConfidenceLevel „Temporarily Paused" / Event 1802 | update firmwaru u OEM | +| **NOT_SUPPORTED** | ConfidenceLevel „Not Supported" | dokumentovat jako výjimku | +| **SETUP_MODE** | Secure Boot v Setup Mode (chybí enrolled PK) | obnovit klíče v UEFI | +| **TASK_MISSING** | chybí servicing úloha (starý build) | nainstalovat KB5066835+ | +| **SECUREBOOT_DISABLED** | Secure Boot vypnutý | rozhodnout o zapnutí | +| **NO_SECUREBOOT / _VM** | Legacy BIOS / VM bez vTPM | výjimka | + +### Remediace (jen po souhlasu, sekvenčně) + +1. **Registry** — nastaví `MicrosoftUpdateManagedOptIn=1`, `AvailableUpdates=0x5944`, `HighConfidenceOptOut=0`; zápis se ověří zpětným čtením, než pokračuje dál. +2. **Servicing úloha** — spustí `\Microsoft\Windows\PI\Secure-Boot-Update` a **čeká na změnu** `AvailableUpdates` (nebo na doběh úlohy, timeout 180 s). +3. **Ověření** — znovu proběhne detekce a ukáže aktualizovaný stav fází. + +Skript pak vypíše další kroky (restart, BitLocker, opakování) — **bez restartu**. + +### Stav napříč restarty + +Průběh se ukládá do `%ProgramData%\SecureBootCA2023\state.json` (číslo cyklu, kategorie, hodnota). S `-RegisterResume` se přes `RunOnce` po dalším restartu sama spustí `-CheckOnly` (žádné auto-aplikování, žádný auto-restart). + +### Výstup + +- Stručný **barevný** souhrn: server, checklist, registry/firmware, verdikt +- Český, faktický, nezahlcuje; důležité informace jsou zvýrazněné +- Volitelný **log** (cesta se vypíše), `-Detailed` rozpis, `-PassThru` objekt +- `-CheckOnly` nastavuje **exit kód** (0/1/2) pro automatizaci + +### Kódování + +Skript je uložen jako **UTF-8 s BOM** a na startu vynutí UTF-8 výstup konzole, aby čeština i symboly (`✓`) fungovaly i ve Windows PowerShell 5.1. Symboly checklistu jsou na jednom místě nahoře ve skriptu (`$SYM_DONE` …), kdyby je bylo potřeba změnit. + +--- + +## 4. Zdroje + +### Microsoft — oficiální dokumentace + +| Téma | Odkaz | +|---|---| +| Přehled: expirace certifikátů a CA aktualizace (KB5062710) | [support.microsoft.com](https://support.microsoft.com/en-us/topic/windows-secure-boot-certificate-expiration-and-ca-updates-7ff40d33-95dc-4c3c-8725-a9b95457578e) | +| Guidance for IT Professionals and Organizations | [support.microsoft.com](https://support.microsoft.com/en-us/topic/secure-boot-certificate-updates-guidance-for-it-professionals-and-organizations-e2b43f9f-b424-42df-bc6a-8476db65ab2f) | +| Registry key updates for Secure Boot (IT-managed) | [support.microsoft.com](https://support.microsoft.com/en-us/topic/registry-key-updates-for-secure-boot-windows-devices-with-it-managed-updates-a7be69c9-4634-42e1-9ca1-df06f43f360d) | +| Secure Boot DB and DBX variable update events | [support.microsoft.com](https://support.microsoft.com/en-us/topic/secure-boot-db-and-dbx-variable-update-events-37e47cf8-608b-4a87-8175-bdead630eb69) | +| IT Pro guidance (KB5062713) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5062713) | +| Registry key metoda (KB5068202) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5068202) | +| GPO metoda (KB5068198) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5068198) | +| WinCS API (KB5068197) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5068197) | +| Troubleshooting (KB5085046) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5085046) | +| Known issues a resolutions, vč. Hyper-V fix (KB5085790) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5085790) | +| Minimální build 14. 10. 2025 (KB5066835) | [support.microsoft.com](https://support.microsoft.com/en-us/topic/october-14-2025-kb5066835-os-builds-26200-6899-and-26100-6899-1db237d8-9f3b-4218-9515-3e0a32729685) | +| Windows Server Secure Boot playbook (2026) | [techcommunity.microsoft.com](https://techcommunity.microsoft.com/blog/windowsservernewsandbestpractices/windows-server-secure-boot-playbook-for-certificates-expiring-in-2026/4495789) | + +### Komunitní projekty (inspirace pro logiku a přehled) + +| Projekt | Odkaz | +|---|---| +| CheckCA2023 (claude-boucher) — GUI monitor, podrobná registry/bit reference | [github.com/claude-boucher/CheckCA2023](https://github.com/claude-boucher/CheckCA2023) | +| SecureBoot-CA2023-Automatic-Update (mathisokle) — automatizace přes restarty | [github.com/mathisokle/SecureBoot-CA2023-Automatic-Update](https://github.com/mathisokle/SecureBoot-CA2023-Automatic-Update) | + +--- + +*Skript záměrně NErestartuje server. Implementuje metodu registry klíčů (KB5068202); metody GPO / Intune / WinCS nepokrývá.*