#Requires -Version 5.1 <# .SYNOPSIS Detekce + interaktivní remediace Secure Boot certifikátů na jednom serveru (KB5062710 / KB5068202). .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ě toho, že se systém reálně bootuje z Boot Manageru podepsaného Windows UEFI CA 2023 (signalizuje WindowsUEFICA2023Capable=2 / Event 1808 / 1799). 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 + exit kód (0=hotovo, 1=nutná akce, 2=blokováno). Bez změn/dotazu. .PARAMETER AssumeYes 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. .PARAMETER SkipScheduledTask Nastaví registry, ale nespustí servicing task (spustí se sám cca á 12 h). .PARAMETER SkipBootManagerFileCheck 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í kontrolu (-CheckOnly). Nikdy nerestartuje sám. .PARAMETER Detailed Rozšířený rozpis (certifikáty, události, boot manager chain, bit-breakdown AvailableUpdates). .PARAMETER PassThru Vrátí výsledný objekt do pipeline. .PARAMETER LogPath Cesta k logu. Default: log vedle skriptu vznikne jen při reálné remediaci. .NOTES Reference: KB5062710, KB5068202, KB5066835 (min. build 10/2025), KB5085790. Vyžaduje Administrator (čtení UEFI databází, mount ESP, zápis registry). #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [switch]$CheckOnly, [switch]$AssumeYes, [switch]$Force, [switch]$SkipScheduledTask, [switch]$SkipBootManagerFileCheck, [switch]$RegisterResume, [switch]$Detailed, [switch]$PassThru, [string]$LogPath ) $ErrorActionPreference = 'SilentlyContinue' Set-StrictMode -Off # ── Konzole: vynutit UTF-8 výstup (diakritika + symboly i ve Windows PowerShell 5.1) ── try { $utf8NoBom = New-Object System.Text.UTF8Encoding($false) [Console]::OutputEncoding = $utf8NoBom $OutputEncoding = $utf8NoBom } catch { } #region ── Konstanty ────────────────────────────────────────────────────────── $REG_SECUREBOOT = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot' $REG_SERVICING = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing' $REG_RUNONCE = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' $AVAILABLE_UPDATES_VALUE = 0x5944 $TASK_PATH = '\Microsoft\Windows\PI\' $TASK_NAME = 'Secure-Boot-Update' $TASK_TIMEOUT_SEC = 180 $WORK_ROOT = Join-Path $env:ProgramData 'SecureBootCA2023' $STATE_FILE = Join-Path $WORK_ROOT 'state.json' $CN_KEK2023 = 'KEK 2K CA 2023' $CN_WINUEFI2023 = 'Windows UEFI CA 2023' $CN_UEFI2023 = 'Microsoft UEFI CA 2023' $CN_OPTROM2023 = 'Option ROM UEFI CA 2023' $CN_PCA2011 = 'Windows Production PCA 2011' # 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 = ' ' #endregion #region ── Log / barevný výstup ─────────────────────────────────────────────── $scriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { (Get-Location).Path } $script:LogFile = if ($LogPath) { $LogPath } else { Join-Path $scriptDir ("SecureBootRemediation-{0}-{1}.log" -f $env:COMPUTERNAME, (Get-Date -Format 'yyyyMMdd-HHmmss')) } $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 { } } function Write-Line { param([string]$Text='', [string]$Color='Gray', [switch]$NoLog) Write-Host $Text -ForegroundColor $Color; if (-not $NoLog) { Add-LogLine $Text } } function Write-Rule { param([string]$Color='DarkCyan') Write-Host ('=' * 64) -ForegroundColor $Color } function Write-Head { param([string]$Text) Write-Host ''; Write-Host $Text -ForegroundColor Cyan; Add-LogLine "== $Text ==" } function Write-KV { param([string]$Key, [string]$Value, [string]$ValueColor='White', [string]$Note, [string]$NoteColor='DarkGray') Write-Host (" {0,-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) } function Write-Check { param([ValidateSet('Done','Pending','Fail','Info')][string]$State, [string]$Text, [string]$Note, [string]$NoteColor='Yellow') switch ($State) { 'Done' { $mark="[$SYM_DONE]"; $mc='Green'; $tc='White' } 'Fail' { $mark="[$SYM_FAIL]"; $mc='Red'; $tc='Red' } 'Pending' { $mark="[$SYM_PENDING]"; $mc='DarkGray'; $tc='Gray' } 'Info' { $mark="[$SYM_PENDING]"; $mc='DarkGray'; $tc='DarkGray' } } Write-Host (" {0} " -f $mark) -ForegroundColor $mc -NoNewline Write-Host $Text -ForegroundColor $tc -NoNewline if ($Note) { Write-Host (" $Note") -ForegroundColor $NoteColor } else { Write-Host '' } Add-LogLine (" [{0}] {1} {2}" -f $State, $Text, $Note) } #endregion #region ── Detekce — prostředí / HW / Secure Boot / 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' } 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' } catch { return 'Unknown' } } function Get-HardwareInfo { $hw = [ordered]@{ Manufacturer='Unknown'; Model='Unknown'; FirmwareType='Unknown'; BiosVersion='Unknown'; BiosReleaseDate='Unknown' } try { $cs = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop; $hw.Manufacturer=[string]$cs.Manufacturer; $hw.Model=[string]$cs.Model } catch { } try { $bios = Get-CimInstance Win32_BIOS -ErrorAction Stop $hw.BiosVersion=[string]$bios.SMBIOSBIOSVersion if ($bios.ReleaseDate) { $hw.BiosReleaseDate = ([datetime]$bios.ReleaseDate).ToString('yyyy-MM-dd') } } catch { } $fw = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue if ($fw) { $hw.FirmwareType = if ($fw.PEFirmwareType -eq 2) { 'UEFI' } else { 'Legacy BIOS' } } elseif (Test-Path $REG_SECUREBOOT) { $hw.FirmwareType='UEFI' } else { $hw.FirmwareType='Legacy BIOS' } return $hw } function Get-SecureBootState { $s = [ordered]@{ IsUEFI=$false; IsSupported=$false; IsEnabled=$false; Error=$null } $fw = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue $s.IsUEFI = ($fw -and $fw.PEFirmwareType -eq 2) -or (Test-Path $REG_SECUREBOOT) if (-not $s.IsUEFI) { return $s } try { $r = Confirm-SecureBootUEFI -ErrorAction Stop; $s.IsSupported=$true; $s.IsEnabled=[bool]$r } catch { $m=$_.Exception.Message if ($m -like '*not supported*' -or $m -like '*Cmdlet not supported*') { $s.IsSupported=$false } elseif ($m -like '*disabled*') { $s.IsSupported=$true; $s.IsEnabled=$false } else { $s.IsSupported=$true; $s.IsEnabled=$false; $s.Error=$m } } return $s } function Get-SecureBootMode { # 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 / registry / events / boot manager ─────────── function Parse-EFISignatureList { param([byte[]]$Bytes) $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 { } } $so += $sz } } $o += $listSize } return $certs } function Convert-CertToInfo { param($Cert) [ordered]@{ Subject=$Cert.Subject; Thumbprint=$Cert.Thumbprint; NotAfter=$Cert.NotAfter.ToString('yyyy-MM-dd') } } function Get-SbVarAscii { param([ValidateSet('PK','KEK','db','dbx')][string]$Name) try { return [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI -Name $Name -ErrorAction Stop).Bytes) } catch { return $null } } function Get-CertificateStatus { $st = [ordered]@{ KEK=[ordered]@{ Has2011=$false; Has2023=$false; Certs2011=@(); Certs2023=@(); Error=$null } DB =[ordered]@{ Has2011UEFI=$false; Has2011WindowsPCA=$false; Has2023UEFI=$false; Has2023OptionROM=$false; Has2023WindowsUEFI=$false; Certs2011=@(); Certs2023=@(); Error=$null } DbxRevokesPCA2011=$false; AnyExpiring2011=$false } try { 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 { $st.KEK.Error=$_.Exception.Message } try { 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 { $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 } } $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 } function Get-RegistryStatus { $reg = [ordered]@{ AvailableUpdates=$null; HighConfidenceOptOut=$null; MicrosoftUpdateManagedOptIn=$null ServicingKeyExists=$false; UEFICA2023Status=$null; UEFICA2023StatusText='KeyNotPresent' UEFICA2023Error=$null; UEFICA2023ErrorEvent=$null; WindowsUEFICA2023Capable=$null WindowsUEFICA2023CapableText='-'; ConfidenceLevel=$null } $m = Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue if ($m) { $reg.AvailableUpdates=$m.AvailableUpdates; $reg.HighConfidenceOptOut=$m.HighConfidenceOptOut $reg.MicrosoftUpdateManagedOptIn=$m.MicrosoftUpdateManagedOptIn } $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 $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 { $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) { $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 -ne 'NoMatchingEventsException') { $e.Error=$_.Exception.Message } } return $e } function Get-TaskExists { try { $null = Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction Stop; return $true } catch { return $false } } function Get-BootManagerStatus { # Ověří, zda je bootmgfw.efi na ESP podepsaný Windows UEFI CA 2023 (staged stav). param([bool]$SkipFileCheck) $r=[ordered]@{ EspHas2023=$false; Evidence=@(); Chain=$null; Signer=$null; Error=$null; Checked=$false } if ($SkipFileCheck) { return $r } $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; $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'))) Copy-Item -LiteralPath $src -Destination $tmp -Force -ErrorAction Stop if (Get-Command certutil.exe -ErrorAction SilentlyContinue) { $raw = & certutil.exe -dump $tmp 2>&1 | Out-String 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 { if ($tmp -and (Test-Path -LiteralPath $tmp)) { Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue } if ($mounted) { & mountvol "$drive`:" /D 2>$null | Out-Null } } return $r } #endregion #region ── Fáze + kategorizace ──────────────────────────────────────────────── 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 $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é)' $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 } switch ($key) { 'Kek2023' { 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 ($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' } } } } return $ph } function Get-RemediationCategory { param($Result) $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 ($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 ($reg.ConfidenceLevel -like 'Not Supported*') { return @{ Code='NOT_SUPPORTED'; Tag='[X]'; Color='Red'; Label='Zařízení nepodporuje automatickou aktualizaci (ConfidenceLevel: Not Supported)' } } if ($reg.ConfidenceLevel -like 'Temporarily Paused*' -or $evt.ById[1802]) { return @{ Code='FIRMWARE_UPDATE_NEEDED'; Tag='[FW]'; Color='Magenta'; Label='Aktualizace pozastavena (známý problém) — zkontrolujte firmware u OEM' } } $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 — 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á 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ů' } } #endregion #region ── Detekce — orchestrace ────────────────────────────────────────────── function Invoke-Detection { param([bool]$SkipBootMgrFile) $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 ($R.SecureBoot.IsUEFI -and $R.SecureBoot.IsSupported) { $R.OperatingMode = Get-SecureBootMode $R.Certificates = Get-CertificateStatus $R.BootManager = Get-BootManagerStatus -SkipFileCheck:$SkipBootMgrFile } else { $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 } } $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 #region ── Výpis ────────────────────────────────────────────────────────────── function Show-DetectionSummary { param($R) $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' $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 ($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' $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 Write-Host $R.CategoryLabel -ForegroundColor $R.CategoryColor Add-LogLine ("VÝSLEDEK: {0}" -f $R.CategoryLabel) } function Show-DetailedDetection { param($R) $c=$R.Certificates; $evt=$R.EventLog; $bm=$R.BootManager; $reg=$R.Registry Write-Head 'PODROBNOSTI' 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)){'Red'}else{'DarkGray'} Write-Host (" [{0}] EventID {1} {2}" -f $e.TimeCreated, $e.EventId, $e.Level) -ForegroundColor $ec } } } #endregion #region ── Plán remediace + souhlas ─────────────────────────────────────────── function Resolve-RemediationPlan { param($R, [bool]$ForceMode) $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 } } 'OK_TRANSITION' { $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.'; $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 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í 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='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.' } } '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.') } '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 } function Get-UserConsent { param([string]$Question, [string]$Detail) if ($AssumeYes) { Write-Line (" {0} -> automaticky ANO (-AssumeYes)" -f $Question) Cyan; return $true } if (-not [Environment]::UserInteractive) { Write-Line ' Neinteraktivní relace bez -AssumeYes — remediace se neprovede.' Yellow; return $false } Write-Host ''; Write-Host $Question -ForegroundColor White if ($Detail) { Write-Host $Detail -ForegroundColor DarkGray } try { $yes=New-Object System.Management.Automation.Host.ChoiceDescription '&Ano','Aplikovat (bez restartu)' $no =New-Object System.Management.Automation.Host.ChoiceDescription '&Ne','Neprovádět změny' return ($Host.UI.PromptForChoice('','Vaše volba:',[System.Management.Automation.Host.ChoiceDescription[]]@($yes,$no),1) -eq 0) } catch { return ((Read-Host 'Aplikovat? [a/N]') -match '^(a|ano|y|yes)$') } } #endregion #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 } catch { } } function Get-ResumeState { if (-not (Test-Path $STATE_FILE)) { return $null }; try { return (Get-Content -LiteralPath $STATE_FILE -Raw | ConvertFrom-Json) } catch { return $null } } function Clear-ResumeState { if (Test-Path $STATE_FILE) { Remove-Item -LiteralPath $STATE_FILE -Force -ErrorAction SilentlyContinue } } 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 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 Write-Line ' Auto-recheck po příštím restartu nastaven (RunOnce, jen kontrola — žádný restart).' Green } catch { Write-Line (" (Auto-recheck se nepodařilo nastavit: {0})" -f $_.Exception.Message) DarkGray } } #endregion #region ── Remediace ────────────────────────────────────────────────────────── function Invoke-Remediation { param([bool]$SkipBootMgrFile) $out=[ordered]@{ Status='Unknown'; Message=''; After=$null; LogFile=$script:LogFile } Write-Head 'REMEDIACE'; Add-LogLine 'REMEDIACE START' 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 $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 } $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; $out.Status='Error'; $out.Message="Zápis registry selhal: $($_.Exception.Message)"; return $out } if ($SkipScheduledTask) { Write-Line ' [2/3] Servicing task přeskočen (-SkipScheduledTask).' DarkGray } 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 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 $el=0; $state='Running'; $changed=$false do { 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 } } } 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 '' Write-Line (' AvailableUpdates: {0}' -f (Get-AvailableUpdatesText $after.Registry.AvailableUpdates)) Cyan foreach ($k in @('Kek2023','Db2023Windows','BootManager2023')) { $p=$after.Phases[$k]; Write-Check -State $p.State -Text $p.Label -Note $p.Note } $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 $out } #endregion #region ── Main ─────────────────────────────────────────────────────────────── $isWhatIf = [bool]$WhatIfPreference Write-Host '' Write-Rule 'Cyan' Write-Host ' SECURE BOOT — KONTROLA A REMEDIACE CERTIFIKÁTŮ' -ForegroundColor White Write-Host (" {0} {1}" -f $env:COMPUTERNAME, (Get-Date -Format 'yyyy-MM-dd HH:mm')) -ForegroundColor DarkGray Write-Rule 'Cyan' $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $isAdmin) { Write-Host ''; Write-Host ' ! Neběží jako Administrator — část kontrol a remediace nebude dostupná.' -ForegroundColor Yellow } $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í Boot Manageru)…' -ForegroundColor DarkGray $result = Invoke-Detection -SkipBootMgrFile:$SkipBootManagerFileCheck.IsPresent Show-DetectionSummary -R $result if ($Detailed) { Show-DetailedDetection -R $result } 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 $code = switch -Wildcard ($result.Category) { '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 } $plan = Resolve-RemediationPlan -R $result -ForceMode:$Force.IsPresent Write-Head 'VYHODNOCENÍ' 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 } } 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) { 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 } 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.' if (Get-UserConsent -Question $q -Detail $d) { $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 } } } 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 { Write-Host ' ČÁSTEČNĚ HOTOVO — proces pokračuje po restartu.' -ForegroundColor Yellow Write-Host ''; Write-Host ' Další kroky:' -ForegroundColor White Write-Host ' 1. Naplánujte RESTART serveru (skript jej záměrně neprovedl).' -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 ' 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 '' if ($PassThru) { return [pscustomobject]@{ Detection=$result; Plan=$plan; Remediation=$remediation } } #endregion