1314 lines
58 KiB
PowerShell
1314 lines
58 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
Detekce a interaktivní remediace Secure Boot certifikátů (KB5062710 / KB5068202).
|
|
|
|
.DESCRIPTION
|
|
Script zkontroluje přítomnost nových 2023 Secure Boot certifikátů, zobrazí stav
|
|
ve čtyřech sekcích a nabídne remediaci.
|
|
|
|
Pokud jsou povinné 2023 certifikáty (Windows UEFI CA 2023 v DB +
|
|
Microsoft Corporation KEK 2K CA 2023 v KEK) PŘED spuštěním remediace přítomny,
|
|
script skončí s informací, že není nutná žádná akce.
|
|
Výjimka: parametr -Force umožní remediaci přesto spustit.
|
|
|
|
Kritéria úspěšné remediace (post-remediation — všechny 4 podmínky):
|
|
1. Windows UEFI CA 2023 v DB + Microsoft Corporation KEK 2K CA 2023 v KEK
|
|
2. AvailableUpdates = 0x0 nebo 0x4000
|
|
3. UEFICA2023Status = "Updated"
|
|
4. UEFICA2023Error = 0 nebo neexistuje
|
|
|
|
Průchod AvailableUpdates: 0x5944 -> 0x5904 -> 0x5104 -> 0x4104 -> 0x4100 -> 0x4000 -> 0x0
|
|
Hodnota 0x5944 se nastavuje pouze jednou (při prvním spuštění nebo po dokončení);
|
|
systém poté bity sám odečítá.
|
|
|
|
.PARAMETER CheckOnly
|
|
Pouze zobrazí stav bez dotazů a změn.
|
|
Exit kódy: 0 = hotovo, 1 = nutná akce, 2 = blokováno.
|
|
|
|
.PARAMETER AssumeYes
|
|
Přeskočí interaktivní dotaz a remediaci rovnou aplikuje (pokud je smysluplná).
|
|
|
|
.PARAMETER Force
|
|
Spustí remediaci i v případě, že povinné certifikáty jsou již přítomny.
|
|
|
|
.PARAMETER AutoRestart
|
|
Po staged Boot Manageru (AU=0x4100) restartuje server automaticky bez dotazu.
|
|
POZOR: server bude restartován okamžitě bez potvrzení.
|
|
|
|
.NOTES
|
|
Reference: KB5062710, KB5068202, KB5066835 (min. build 10/2025), KB5085790.
|
|
Vyžaduje Administrator pro zápis registry a spuštění servicing task.
|
|
#>
|
|
|
|
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
|
|
param(
|
|
[switch]$CheckOnly,
|
|
[switch]$AssumeYes,
|
|
[switch]$Force,
|
|
[switch]$AutoRestart
|
|
)
|
|
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
Set-StrictMode -Off
|
|
|
|
# Vynutit UTF-8 výstup (diakritika i ve Windows PowerShell 5.1)
|
|
try {
|
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
|
[Console]::OutputEncoding = $utf8NoBom
|
|
$OutputEncoding = $utf8NoBom
|
|
} catch { }
|
|
|
|
#region ── Konstanty ──────────────────────────────────────────────────────────
|
|
|
|
$REG_SECUREBOOT = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot'
|
|
$REG_SERVICING = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing'
|
|
$AVAILABLE_UPDATES_VALUE = 0x5944
|
|
$TASK_PATH = '\Microsoft\Windows\PI\'
|
|
$TASK_NAME = 'Secure-Boot-Update'
|
|
$TASK_TIMEOUT_SEC = 180
|
|
$WORK_ROOT = Join-Path $env:ProgramData 'SecureBootCA2023'
|
|
$STATE_FILE = Join-Path $WORK_ROOT 'state.json'
|
|
|
|
$CN_KEK2023 = 'KEK 2K CA 2023'
|
|
$CN_WINUEFI2023 = 'Windows UEFI CA 2023'
|
|
$CN_UEFI2023 = 'Microsoft UEFI CA 2023'
|
|
$CN_OPTROM2023 = 'Option ROM UEFI CA 2023'
|
|
$CN_PCA2011 = 'Windows Production PCA 2011'
|
|
|
|
$SYM_DONE = '+'
|
|
$SYM_FAIL = '!'
|
|
$SYM_PENDING = ' '
|
|
|
|
# Popisy EventID pro sekci UDÁLOSTI
|
|
$EVENT_DESC = @{
|
|
1795 = 'Chyba firmwaru — aktualizace selhala'
|
|
1796 = 'Selhání zápisu UEFI proměnné'
|
|
1799 = 'Informační: aktualizace dokončena'
|
|
1800 = 'Aktualizace zahájena'
|
|
1801 = 'Čeká na podmínky (monitor)'
|
|
1802 = 'Pozastaveno — problém firmwaru'
|
|
1803 = 'Odloženo (monitorovací podmínka)'
|
|
1808 = 'Certifikáty úspěšně aplikovány'
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Log / barevný výstup ───────────────────────────────────────────────
|
|
|
|
$script:LogFile = Join-Path $WORK_ROOT 'SecureBootRemediation.log'
|
|
$script:LogBuffer = New-Object System.Collections.Generic.List[string]
|
|
|
|
function Add-LogLine {
|
|
param([string]$Text)
|
|
[void]$script:LogBuffer.Add(('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Text))
|
|
}
|
|
|
|
function Flush-Log {
|
|
if ($script:LogBuffer.Count -eq 0) { return }
|
|
try {
|
|
if (-not (Test-Path $WORK_ROOT)) { New-Item -Path $WORK_ROOT -ItemType Directory -Force | Out-Null }
|
|
[System.IO.File]::AppendAllText(
|
|
$script:LogFile,
|
|
(($script:LogBuffer -join "`r`n") + "`r`n"),
|
|
(New-Object System.Text.UTF8Encoding($false))
|
|
)
|
|
$script:LogBuffer.Clear()
|
|
} catch { }
|
|
}
|
|
|
|
function Write-Line {
|
|
param([string]$Text = '', [string]$Color = 'Gray', [switch]$NoLog)
|
|
Write-Host $Text -ForegroundColor $Color
|
|
if (-not $NoLog) { Add-LogLine $Text }
|
|
}
|
|
|
|
function Write-Rule {
|
|
param([string]$Color = 'DarkCyan')
|
|
Write-Host ('=' * 64) -ForegroundColor $Color
|
|
}
|
|
|
|
function Write-Head {
|
|
param([string]$Text)
|
|
Write-Host ''
|
|
Write-Host $Text -ForegroundColor Cyan
|
|
Add-LogLine "== $Text =="
|
|
}
|
|
|
|
function Write-KV {
|
|
param(
|
|
[string]$Key,
|
|
[string]$Value,
|
|
[string]$ValueColor = 'White',
|
|
[string]$Note,
|
|
[string]$NoteColor = 'DarkGray'
|
|
)
|
|
Write-Host (" {0,-28}: " -f $Key) -ForegroundColor Gray -NoNewline
|
|
Write-Host $Value -ForegroundColor $ValueColor -NoNewline
|
|
if ($Note) { Write-Host (" $Note") -ForegroundColor $NoteColor } else { Write-Host '' }
|
|
Add-LogLine ("{0}: {1} {2}" -f $Key, $Value, $Note)
|
|
}
|
|
|
|
function Write-Check {
|
|
param(
|
|
[ValidateSet('Done','Pending','Fail','Info')][string]$State,
|
|
[string]$Text,
|
|
[string]$Note,
|
|
[string]$NoteColor = 'Yellow'
|
|
)
|
|
switch ($State) {
|
|
'Done' { $mark = "[$SYM_DONE]"; $mc = 'Green'; $tc = 'White' }
|
|
'Fail' { $mark = "[$SYM_FAIL]"; $mc = 'Red'; $tc = 'Red' }
|
|
'Pending' { $mark = "[ ]"; $mc = 'DarkGray'; $tc = 'Gray' }
|
|
'Info' { $mark = "[ ]"; $mc = 'DarkGray'; $tc = 'DarkGray' }
|
|
}
|
|
Write-Host (" {0} " -f $mark) -ForegroundColor $mc -NoNewline
|
|
Write-Host $Text -ForegroundColor $tc -NoNewline
|
|
if ($Note) { Write-Host (" $Note") -ForegroundColor $NoteColor } else { Write-Host '' }
|
|
Add-LogLine (" [{0}] {1} {2}" -f $State, $Text, $Note)
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Detekce — prostředí / HW / Secure Boot ────────────────────────────
|
|
|
|
function Get-EnvironmentType {
|
|
try {
|
|
$cs = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
|
|
$mfr = [string]$cs.Manufacturer
|
|
$mdl = [string]$cs.Model
|
|
if ($mfr -like '*VMware*') { return 'VMware VM' }
|
|
if ($mfr -like '*Microsoft*' -and $mdl -eq 'Virtual Machine') { return 'Hyper-V VM' }
|
|
if ($mdl -like '*Virtual*' -or $mfr -like '*QEMU*' -or $mfr -like '*Xen*') { return 'Other VM' }
|
|
return 'Physical'
|
|
} catch { return 'Unknown' }
|
|
}
|
|
|
|
function Get-HardwareInfo {
|
|
$hw = [ordered]@{
|
|
Manufacturer = 'Unknown'
|
|
Model = 'Unknown'
|
|
FirmwareType = 'Unknown'
|
|
BiosVersion = 'Unknown'
|
|
BiosReleaseDate = 'Unknown'
|
|
}
|
|
try {
|
|
$cs = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
|
|
$hw.Manufacturer = [string]$cs.Manufacturer
|
|
$hw.Model = [string]$cs.Model
|
|
} catch { }
|
|
try {
|
|
$bios = Get-CimInstance Win32_BIOS -ErrorAction Stop
|
|
$hw.BiosVersion = [string]$bios.SMBIOSBIOSVersion
|
|
if ($bios.ReleaseDate) {
|
|
$hw.BiosReleaseDate = ([datetime]$bios.ReleaseDate).ToString('yyyy-MM-dd')
|
|
}
|
|
} catch { }
|
|
$fw = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue
|
|
if ($fw) {
|
|
$hw.FirmwareType = if ($fw.PEFirmwareType -eq 2) { 'UEFI' } else { 'Legacy BIOS' }
|
|
} elseif (Test-Path $REG_SECUREBOOT) {
|
|
$hw.FirmwareType = 'UEFI'
|
|
} else {
|
|
$hw.FirmwareType = 'Legacy BIOS'
|
|
}
|
|
return $hw
|
|
}
|
|
|
|
function Get-SecureBootState {
|
|
$s = [ordered]@{ IsUEFI = $false; IsSupported = $false; IsEnabled = $false; Error = $null }
|
|
$fw = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue
|
|
$s.IsUEFI = ($fw -and $fw.PEFirmwareType -eq 2) -or (Test-Path $REG_SECUREBOOT)
|
|
if (-not $s.IsUEFI) { return $s }
|
|
try {
|
|
$r = Confirm-SecureBootUEFI -ErrorAction Stop
|
|
$s.IsSupported = $true
|
|
$s.IsEnabled = [bool]$r
|
|
} catch {
|
|
$m = $_.Exception.Message
|
|
if ($m -like '*not supported*' -or $m -like '*Cmdlet not supported*') {
|
|
$s.IsSupported = $false
|
|
} elseif ($m -like '*disabled*') {
|
|
$s.IsSupported = $true; $s.IsEnabled = $false
|
|
} else {
|
|
$s.IsSupported = $true; $s.IsEnabled = $false; $s.Error = $m
|
|
}
|
|
}
|
|
return $s
|
|
}
|
|
|
|
function Get-SecureBootMode {
|
|
$setup = $null; $audit = $null; $deployed = $null
|
|
try { $setup = (Get-SecureBootUEFI -Name SetupMode -ErrorAction Stop).Bytes[0] } catch { }
|
|
try { $audit = (Get-SecureBootUEFI -Name AuditMode -ErrorAction Stop).Bytes[0] } catch { }
|
|
try { $deployed = (Get-SecureBootUEFI -Name DeployedMode -ErrorAction Stop).Bytes[0] } catch { }
|
|
if ($setup -eq 1) { return 'Setup' }
|
|
if ($audit -eq 1) { return 'Audit' }
|
|
if ($deployed -eq 1) { return 'Deployed' }
|
|
if ($null -ne $setup) { return 'User' }
|
|
return 'Unknown'
|
|
}
|
|
|
|
function Get-BitLockerInfo {
|
|
$i = [ordered]@{ Status = 'Unknown'; Protectors = ''; UsesPcr = $false }
|
|
try {
|
|
$v = Get-BitLockerVolume -MountPoint $env:SystemDrive -ErrorAction Stop
|
|
if ($v) {
|
|
$i.Status = [string]$v.ProtectionStatus
|
|
$types = @($v.KeyProtector | ForEach-Object { [string]$_.KeyProtectorType })
|
|
$i.Protectors = ($types -join ', ')
|
|
$i.UsesPcr = [bool](@($types | Where-Object { $_ -like 'Tpm*' }).Count)
|
|
}
|
|
} catch { $i.Status = 'N/A' }
|
|
return $i
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Detekce — certifikáty / registry / události ───────────────────────
|
|
|
|
function Parse-EFISignatureList {
|
|
# Parsuje EFI_SIGNATURE_LIST a vrátí pole X509Certificate2 (pouze X.509 položky).
|
|
# Formát: opakující se hlavičky SignatureList, každá obsahuje seznam SignatureEntry
|
|
# s 16B GUID prefixem (SignatureOwner) + DER-encoded certifikát.
|
|
param([byte[]]$Bytes)
|
|
$certs = @()
|
|
if (-not $Bytes -or $Bytes.Length -lt 28) { return $certs }
|
|
|
|
# EFI_CERT_X509_GUID: {a5c059a1-94e4-4aa7-87b5-ab155c2bf072} (little-endian)
|
|
$x509Guid = [byte[]](0xa1,0x59,0xc0,0xa5,0xe4,0x94,0xa7,0x4a,0x87,0xb5,0xab,0x15,0x5c,0x2b,0xf0,0x72)
|
|
|
|
$pos = 0
|
|
while ($pos + 28 -le $Bytes.Length) {
|
|
$sigTypeGuid = $Bytes[$pos..($pos+15)]
|
|
$listSize = [BitConverter]::ToUInt32($Bytes, $pos+16)
|
|
$headerSize = [BitConverter]::ToUInt32($Bytes, $pos+20)
|
|
$sigSize = [BitConverter]::ToUInt32($Bytes, $pos+24)
|
|
|
|
if ($listSize -lt 28 -or $listSize -gt ($Bytes.Length - $pos)) { break }
|
|
|
|
$isX509 = $true
|
|
for ($i = 0; $i -lt 16; $i++) {
|
|
if ($sigTypeGuid[$i] -ne $x509Guid[$i]) { $isX509 = $false; break }
|
|
}
|
|
|
|
if ($isX509 -and $sigSize -gt 16) {
|
|
$entryPos = $pos + 28 + $headerSize
|
|
$listEnd = $pos + $listSize
|
|
while ($entryPos + $sigSize -le $listEnd) {
|
|
$certOffset = $entryPos + 16 # přeskočit 16B SignatureOwner GUID
|
|
$certSize = [int]$sigSize - 16
|
|
if ($certOffset + $certSize -le $Bytes.Length -and $certSize -gt 0) {
|
|
try {
|
|
$certBytes = [byte[]]($Bytes[$certOffset..($certOffset + $certSize - 1)])
|
|
$certs += New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(, $certBytes)
|
|
} catch { }
|
|
}
|
|
$entryPos += $sigSize
|
|
}
|
|
}
|
|
$pos += $listSize
|
|
}
|
|
return $certs
|
|
}
|
|
|
|
function Convert-CertToInfo {
|
|
param($Cert)
|
|
[ordered]@{
|
|
Subject = $Cert.Subject
|
|
Thumbprint = $Cert.Thumbprint
|
|
NotAfter = $Cert.NotAfter.ToString('yyyy-MM-dd')
|
|
}
|
|
}
|
|
|
|
function Get-SbVarAscii {
|
|
param([ValidateSet('PK','KEK','db','dbx')][string]$Name)
|
|
try {
|
|
return [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI -Name $Name -ErrorAction Stop).Bytes)
|
|
} catch { return $null }
|
|
}
|
|
|
|
function Get-CertificateStatus {
|
|
$st = [ordered]@{
|
|
KEK = [ordered]@{
|
|
Has2011 = $false; Has2023 = $false
|
|
Certs2011 = @(); Certs2023 = @()
|
|
Error = $null
|
|
}
|
|
DB = [ordered]@{
|
|
Has2011UEFI = $false; Has2011WindowsPCA = $false
|
|
Has2023UEFI = $false; Has2023OptionROM = $false
|
|
Has2023WindowsUEFI = $false
|
|
Certs2011 = @(); Certs2023 = @()
|
|
Error = $null
|
|
}
|
|
DbxRevokesPCA2011 = $false
|
|
AnyExpiring2011 = $false
|
|
}
|
|
|
|
try {
|
|
$kekBytes = (Get-SecureBootUEFI -Name KEK -ErrorAction Stop).Bytes
|
|
foreach ($cert in (Parse-EFISignatureList -Bytes $kekBytes)) {
|
|
$subj = $cert.Subject
|
|
$info = Convert-CertToInfo $cert
|
|
if ($subj -like '*KEK CA 2011*') {
|
|
$st.KEK.Has2011 = $true; $st.KEK.Certs2011 += $info
|
|
}
|
|
if ($subj -like "*$CN_KEK2023*" -or $subj -like '*KEK*CA 2023*') {
|
|
$st.KEK.Has2023 = $true; $st.KEK.Certs2023 += $info
|
|
}
|
|
}
|
|
} catch { $st.KEK.Error = $_.Exception.Message }
|
|
|
|
try {
|
|
$dbBytes = (Get-SecureBootUEFI -Name db -ErrorAction Stop).Bytes
|
|
foreach ($cert in (Parse-EFISignatureList -Bytes $dbBytes)) {
|
|
$subj = $cert.Subject
|
|
$info = Convert-CertToInfo $cert
|
|
if ($subj -like '*UEFI CA 2011*') {
|
|
$st.DB.Has2011UEFI = $true; $st.DB.Certs2011 += $info
|
|
}
|
|
if ($subj -like '*Windows Production PCA 2011*' -or $subj -like '*Windows PCA 2011*') {
|
|
$st.DB.Has2011WindowsPCA = $true; $st.DB.Certs2011 += $info
|
|
}
|
|
if ($subj -like "*$CN_UEFI2023*" -and $subj -notlike '*Option ROM*' -and $subj -notlike '*Windows UEFI*') {
|
|
$st.DB.Has2023UEFI = $true; $st.DB.Certs2023 += $info
|
|
}
|
|
if ($subj -like "*$CN_OPTROM2023*") {
|
|
$st.DB.Has2023OptionROM = $true; $st.DB.Certs2023 += $info
|
|
}
|
|
if ($subj -like "*$CN_WINUEFI2023*") {
|
|
$st.DB.Has2023WindowsUEFI = $true; $st.DB.Certs2023 += $info
|
|
}
|
|
}
|
|
} catch { $st.DB.Error = $_.Exception.Message }
|
|
|
|
# ASCII fallback — doplní booleany pokud X.509 parse selhal
|
|
$kAscii = Get-SbVarAscii KEK
|
|
if ($kAscii -and $kAscii -match [regex]::Escape($CN_KEK2023)) { $st.KEK.Has2023 = $true }
|
|
|
|
$dAscii = Get-SbVarAscii db
|
|
if ($dAscii) {
|
|
if ($dAscii -match [regex]::Escape($CN_WINUEFI2023)) { $st.DB.Has2023WindowsUEFI = $true }
|
|
if ($dAscii -match [regex]::Escape($CN_OPTROM2023)) { $st.DB.Has2023OptionROM = $true }
|
|
if ($dAscii -match 'Microsoft UEFI CA 2023') { $st.DB.Has2023UEFI = $true }
|
|
}
|
|
|
|
$xAscii = Get-SbVarAscii dbx
|
|
if ($xAscii -and $xAscii -match [regex]::Escape($CN_PCA2011)) { $st.DbxRevokesPCA2011 = $true }
|
|
|
|
$st.AnyExpiring2011 = $st.KEK.Has2011 -or $st.DB.Has2011UEFI -or $st.DB.Has2011WindowsPCA
|
|
return $st
|
|
}
|
|
|
|
function Get-RegistryStatus {
|
|
$reg = [ordered]@{
|
|
AvailableUpdates = $null
|
|
HighConfidenceOptOut = $null
|
|
MicrosoftUpdateManagedOptIn = $null
|
|
ServicingKeyExists = $false
|
|
UEFICA2023Status = $null
|
|
UEFICA2023StatusText = 'KeyNotPresent'
|
|
UEFICA2023Error = $null
|
|
UEFICA2023ErrorEvent = $null
|
|
WindowsUEFICA2023Capable = $null
|
|
WindowsUEFICA2023CapableText = '-'
|
|
ConfidenceLevel = $null
|
|
}
|
|
|
|
$main = Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue
|
|
if ($main) {
|
|
$reg.AvailableUpdates = $main.AvailableUpdates
|
|
$reg.HighConfidenceOptOut = $main.HighConfidenceOptOut
|
|
$reg.MicrosoftUpdateManagedOptIn = $main.MicrosoftUpdateManagedOptIn
|
|
}
|
|
|
|
$serv = Get-ItemProperty $REG_SERVICING -ErrorAction SilentlyContinue
|
|
if ($serv) {
|
|
$reg.ServicingKeyExists = $true
|
|
$reg.UEFICA2023Status = $serv.UEFICA2023Status
|
|
$reg.UEFICA2023Error = $serv.UEFICA2023Error
|
|
$reg.UEFICA2023ErrorEvent = $serv.UEFICA2023ErrorEvent
|
|
$reg.WindowsUEFICA2023Capable = $serv.WindowsUEFICA2023Capable
|
|
$reg.ConfidenceLevel = $serv.ConfidenceLevel
|
|
}
|
|
|
|
# UEFICA2023Status je REG_SZ (NotStarted/InProgress/Updated); defensivně i číselná varianta
|
|
$sv = $reg.UEFICA2023Status
|
|
if ($null -eq $sv) {
|
|
$reg.UEFICA2023StatusText = 'KeyNotPresent'
|
|
} elseif ($sv -is [string] -and $sv -ne '') {
|
|
$reg.UEFICA2023StatusText = $sv
|
|
} else {
|
|
$reg.UEFICA2023StatusText = switch ([int]$sv) {
|
|
0 { 'NotStarted' } 1 { 'InProgress' } 2 { 'Updated' } 3 { 'Failed' } default { "($sv)" }
|
|
}
|
|
}
|
|
|
|
$reg.WindowsUEFICA2023CapableText = switch ($reg.WindowsUEFICA2023Capable) {
|
|
0 { '0 — Windows UEFI CA 2023 není v DB' }
|
|
1 { '1 — certifikát v DB (boot manager zatím ne)' }
|
|
2 { '2 — bootuje se z 2023 boot manageru' }
|
|
$null { '-' }
|
|
default { [string]$reg.WindowsUEFICA2023Capable }
|
|
}
|
|
|
|
return $reg
|
|
}
|
|
|
|
function Get-AvailableUpdatesText {
|
|
param($v)
|
|
if ($null -eq $v) { return '(nenastaveno)' }
|
|
$iv = [int]$v
|
|
$hex = '0x{0:X}' -f $iv
|
|
$name = switch ($iv) {
|
|
0 { 'vše hotovo (proces dokončen)' }
|
|
0x4000 { 'vše aplikováno — task čistí guard bit' }
|
|
0x4100 { 'Boot Manager 2023 nasazen na ESP — čeká RESTART' }
|
|
0x4104 { 'Microsoft UEFI CA 2023 v DB' }
|
|
0x5104 { 'Option ROM UEFI CA 2023 v DB' }
|
|
0x5904 { 'Windows UEFI CA 2023 v DB' }
|
|
0x5944 { 'naplánována plná sada (start)' }
|
|
default { 'probíhá aplikace' }
|
|
}
|
|
return "$hex ($name)"
|
|
}
|
|
|
|
function Get-EventLogStatus {
|
|
$e = [ordered]@{
|
|
LastEventId = $null
|
|
LastEventTime = $null
|
|
RelevantEvents = @()
|
|
ById = @{}
|
|
Error = $null
|
|
}
|
|
$ids = @(1795,1796,1799,1800,1801,1802,1803,1808)
|
|
try {
|
|
$events = Get-WinEvent -FilterHashtable @{ LogName='System'; Id=$ids } -MaxEvents 40 -ErrorAction Stop
|
|
if ($events) {
|
|
$sorted = $events | Sort-Object TimeCreated -Descending
|
|
foreach ($id in $ids) {
|
|
$hit = $sorted | Where-Object { $_.Id -eq $id } | Select-Object -First 1
|
|
if ($hit) { $e.ById[$id] = @{ Time = $hit.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') } }
|
|
}
|
|
foreach ($ev in ($sorted | Select-Object -First 10)) {
|
|
$e.RelevantEvents += [ordered]@{
|
|
EventId = $ev.Id
|
|
TimeCreated = $ev.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
|
|
Level = $ev.LevelDisplayName
|
|
}
|
|
}
|
|
$last = $sorted | Select-Object -First 1
|
|
$e.LastEventId = $last.Id
|
|
$e.LastEventTime = $last.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
|
|
}
|
|
} catch {
|
|
if ($_.CategoryInfo.Reason -ne 'NoMatchingEventsException') { $e.Error = $_.Exception.Message }
|
|
}
|
|
return $e
|
|
}
|
|
|
|
function Get-TaskExists {
|
|
try {
|
|
$null = Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction Stop
|
|
return $true
|
|
} catch { return $false }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Kategorizace ───────────────────────────────────────────────────────
|
|
|
|
function Test-RemediationComplete {
|
|
# Ověří všechny 4 post-remediation podmínky úspěchu.
|
|
param($R)
|
|
$cert = $R.Certificates
|
|
$reg = $R.Registry
|
|
$certsOk = $cert.KEK.Has2023 -and $cert.DB.Has2023WindowsUEFI
|
|
$auVal = [int]$reg.AvailableUpdates
|
|
$auOk = ($null -ne $reg.AvailableUpdates) -and ($auVal -eq 0 -or $auVal -eq 0x4000)
|
|
$statusOk = $reg.UEFICA2023StatusText -eq 'Updated'
|
|
$errorOk = ($null -eq $reg.UEFICA2023Error) -or ([int]$reg.UEFICA2023Error -eq 0)
|
|
return $certsOk -and $auOk -and $statusOk -and $errorOk
|
|
}
|
|
|
|
function Get-RemediationCategory {
|
|
param($Result)
|
|
$sb = $Result.SecureBoot
|
|
$reg = $Result.Registry
|
|
$evt = $Result.EventLog
|
|
$cert = $Result.Certificates
|
|
$env = $Result.EnvironmentType
|
|
$mode = $Result.OperatingMode
|
|
|
|
# ── Základní blokátory ────────────────────────────────────────────────────
|
|
if (-not $sb.IsUEFI -or -not $sb.IsSupported) {
|
|
if ($env -like '*VM*') {
|
|
return @{ Code='NO_SECUREBOOT_VM'; Color='DarkGray'
|
|
Label='Secure Boot nepodporováno — VM bez vTPM nebo UEFI' }
|
|
}
|
|
return @{ Code='NO_SECUREBOOT'; Color='DarkGray'
|
|
Label='Secure Boot nepodporováno — Legacy BIOS' }
|
|
}
|
|
if (-not $sb.IsEnabled) {
|
|
return @{ Code='SECUREBOOT_DISABLED'; Color='Yellow'
|
|
Label='Secure Boot je vypnutý' }
|
|
}
|
|
if ($mode -eq 'Setup') {
|
|
return @{ Code='SETUP_MODE'; Color='Magenta'
|
|
Label='Secure Boot v Setup Mode — aktualizaci nelze dokončit (chybí enrolled PK)' }
|
|
}
|
|
if (-not $Result.TaskExists) {
|
|
return @{ Code='TASK_MISSING'; Color='Magenta'
|
|
Label='Chybí servicing task — nainstalujte kumulativní update min. z 10/2025 (KB5066835)' }
|
|
}
|
|
|
|
# ── Chyby a blokující stavy ───────────────────────────────────────────────
|
|
$hasErrorEvent = $reg.UEFICA2023ErrorEvent -and ([int]$reg.UEFICA2023ErrorEvent -ne 0)
|
|
if ($evt.ById[1795] -or $hasErrorEvent -or $reg.UEFICA2023StatusText -eq 'Failed') {
|
|
return @{ Code='UPDATE_FAILED'; Color='Red'
|
|
Label='Selhání aktualizace — chyba firmwaru nebo UEFICA2023ErrorEvent' }
|
|
}
|
|
if ($reg.ConfidenceLevel -like 'Not Supported*') {
|
|
return @{ Code='NOT_SUPPORTED'; Color='Red'
|
|
Label='Zařízení nepodporuje automatickou aktualizaci (ConfidenceLevel: Not Supported)' }
|
|
}
|
|
if ($reg.ConfidenceLevel -like 'Temporarily Paused*' -or $evt.ById[1802]) {
|
|
return @{ Code='FIRMWARE_UPDATE_NEEDED'; Color='Magenta'
|
|
Label='Aktualizace pozastavena — problém firmwaru, zkontrolujte update u OEM' }
|
|
}
|
|
|
|
# ── Hotovo (všechny 4 post-remediation podmínky) ──────────────────────────
|
|
if (Test-RemediationComplete -R $Result) {
|
|
if ($cert.AnyExpiring2011) {
|
|
return @{ Code='OK_TRANSITION'; Color='Green'
|
|
Label='HOTOVO — 2023 certifikáty nasazeny (staré 2011 ještě přítomné, je to normální)' }
|
|
}
|
|
return @{ Code='OK'; Color='Green'
|
|
Label='HOTOVO — 2023 certifikáty nasazeny, servisování dokončeno' }
|
|
}
|
|
|
|
# ── Boot Manager staged, čeká na restart ─────────────────────────────────
|
|
if ([int]$reg.AvailableUpdates -eq 0x4100) {
|
|
return @{ Code='UPDATE_PENDING_RESTART'; Color='Yellow'
|
|
Label='Boot Manager 2023 nasazen (staged) — čeká na RESTART' }
|
|
}
|
|
|
|
$certsRequiredOk = $cert.KEK.Has2023 -and $cert.DB.Has2023WindowsUEFI
|
|
$auNow = [int]$reg.AvailableUpdates
|
|
$errOk = ($null -eq $reg.UEFICA2023Error) -or ([int]$reg.UEFICA2023Error -eq 0)
|
|
|
|
# ── Certy přítomny + AU=0x0 → hotovo ─────────────────────────────────────
|
|
# UEFICA2023Status může být NotStarted u strojů kde certy byly nainstalovány
|
|
# starší cestou (před zavedením nové servicing infrastruktury). Pokud jsou
|
|
# požadované certifikáty v DB/KEK a AvailableUpdates=0x0, považujeme za hotovo.
|
|
if ($certsRequiredOk -and $auNow -eq 0 -and $errOk) {
|
|
if ($cert.AnyExpiring2011) {
|
|
return @{ Code='OK_TRANSITION'; Color='Green'
|
|
Label='HOTOVO — 2023 certifikáty nasazeny (staré 2011 ještě přítomné, je to normální)' }
|
|
}
|
|
return @{ Code='OK'; Color='Green'
|
|
Label='HOTOVO — 2023 certifikáty nasazeny, AvailableUpdates=0x0' }
|
|
}
|
|
|
|
# ── Starý build: certy přítomny, ale servicing infrastruktura chybí ──────
|
|
$certsPresent = $cert.KEK.Has2023 -or $cert.DB.Has2023WindowsUEFI
|
|
$bmStagedAU = ($auNow -eq 0x4100 -or $auNow -eq 0x4000)
|
|
if ($certsPresent -and -not $bmStagedAU `
|
|
-and ($null -eq $reg.WindowsUEFICA2023Capable) `
|
|
-and ($null -eq $reg.UEFICA2023Status)) {
|
|
return @{ Code='BUILD_OUTDATED'; Color='Magenta'
|
|
Label='Build příliš starý — chybí servicing pro Boot Manager 2023, nutný Windows Update' }
|
|
}
|
|
|
|
# ── Částečný postup / nutná aktualizace ──────────────────────────────────
|
|
if ($certsPresent) {
|
|
return @{ Code='UPDATE_PARTIAL'; Color='Cyan'
|
|
Label='Aktualizace probíhá — certifikáty nasazeny, zbývá dokončit servisování' }
|
|
}
|
|
|
|
return @{ Code='UPDATE_NEEDED'; Color='Yellow'
|
|
Label='Povinné certifikáty 2023 nejsou přítomny — nutná aktualizace' }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Detekce — orchestrace ──────────────────────────────────────────────
|
|
|
|
function Invoke-Detection {
|
|
$os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
|
|
$osBuildFull = try {
|
|
$cv = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction Stop
|
|
'{0}.{1}' -f $cv.CurrentBuildNumber, $cv.UBR
|
|
} catch {
|
|
if ($os) { [string]$os.BuildNumber } else { '?' }
|
|
}
|
|
|
|
$R = [ordered]@{
|
|
AuditTimestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
|
|
Hostname = $env:COMPUTERNAME
|
|
OSCaption = if ($os) { $os.Caption } else { $null }
|
|
OSBuildFull = $osBuildFull
|
|
EnvironmentType = Get-EnvironmentType
|
|
Hardware = Get-HardwareInfo
|
|
SecureBoot = Get-SecureBootState
|
|
OperatingMode = 'Unknown'
|
|
BitLocker = $null
|
|
Certificates = $null
|
|
Registry = Get-RegistryStatus
|
|
EventLog = Get-EventLogStatus
|
|
TaskExists = Get-TaskExists
|
|
Category = $null
|
|
CategoryLabel = $null
|
|
CategoryColor = $null
|
|
}
|
|
|
|
if ($R.SecureBoot.IsUEFI -and $R.SecureBoot.IsSupported) {
|
|
$R.OperatingMode = Get-SecureBootMode
|
|
$R.Certificates = Get-CertificateStatus
|
|
} else {
|
|
$R.Certificates = [ordered]@{
|
|
KEK = [ordered]@{
|
|
Has2011 = $false; Has2023 = $false
|
|
Certs2011 = @(); Certs2023 = @()
|
|
Error = 'Secure Boot nedostupný'
|
|
}
|
|
DB = [ordered]@{
|
|
Has2011UEFI = $false; Has2011WindowsPCA = $false
|
|
Has2023UEFI = $false; Has2023OptionROM = $false
|
|
Has2023WindowsUEFI = $false
|
|
Certs2011 = @(); Certs2023 = @()
|
|
Error = 'Secure Boot nedostupný'
|
|
}
|
|
DbxRevokesPCA2011 = $false
|
|
AnyExpiring2011 = $false
|
|
}
|
|
}
|
|
|
|
$R.BitLocker = Get-BitLockerInfo
|
|
|
|
$cat = Get-RemediationCategory -Result $R
|
|
$R.Category = $cat.Code
|
|
$R.CategoryLabel = $cat.Label
|
|
$R.CategoryColor = $cat.Color
|
|
|
|
return $R
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Zobrazení ──────────────────────────────────────────────────────────
|
|
|
|
function Show-Prerequisites {
|
|
param($Prereqs)
|
|
Write-Head 'PŘEDPOKLADY'
|
|
$i = 1
|
|
foreach ($p in $Prereqs) {
|
|
$state = if ($p.Ok) { 'Done' } elseif ($p.Hard) { 'Fail' } else { 'Info' }
|
|
$nc = if ($p.Ok) { 'DarkGray' } elseif ($p.Hard) { 'Red' } else { 'Yellow' }
|
|
Write-Check -State $state -Text ("{0}. {1}" -f $i, $p.Label) -Note $p.Note -NoteColor $nc
|
|
$i++
|
|
}
|
|
$hardFails = @($Prereqs | Where-Object { $_.Hard -and -not $_.Ok })
|
|
if ($hardFails.Count) {
|
|
Write-Host ''
|
|
Write-Host ' ! Před spuštěním remediace vyřešte výše označené problémy.' -ForegroundColor Red
|
|
} else {
|
|
Write-Host ''
|
|
Write-Host ' Všechny předpoklady splněny.' -ForegroundColor Green
|
|
}
|
|
}
|
|
|
|
function Show-Certificates {
|
|
param($Cert)
|
|
Write-Head 'CERTIFIKÁTY'
|
|
|
|
# Najdi expiry datum pro přítomné povinné certifikáty
|
|
$winUefiExpiry = $null
|
|
$kekExpiry = $null
|
|
foreach ($c in $Cert.DB.Certs2023) {
|
|
if ($c.Subject -like "*$CN_WINUEFI2023*" -and -not $winUefiExpiry) { $winUefiExpiry = $c.NotAfter }
|
|
}
|
|
foreach ($c in $Cert.KEK.Certs2023) {
|
|
if ($c.Subject -like "*$CN_KEK2023*" -and -not $kekExpiry) { $kekExpiry = $c.NotAfter }
|
|
}
|
|
|
|
$items = @(
|
|
@{ Label='Windows UEFI CA 2023'; Present=$Cert.DB.Has2023WindowsUEFI; Required=$true; NotAfter=$winUefiExpiry }
|
|
@{ Label='Microsoft Corporation KEK 2K CA 2023'; Present=$Cert.KEK.Has2023; Required=$true; NotAfter=$kekExpiry }
|
|
@{ Label='Microsoft UEFI CA 2023'; Present=$Cert.DB.Has2023UEFI; Required=$false; NotAfter=$null }
|
|
@{ Label='Microsoft Option ROM UEFI CA 2023'; Present=$Cert.DB.Has2023OptionROM; Required=$false; NotAfter=$null }
|
|
)
|
|
|
|
foreach ($item in $items) {
|
|
$tag = if ($item.Required) { '(povinný) ' } else { '(volitelný)' }
|
|
$expiry = if ($item.NotAfter) { " platný do $($item.NotAfter)" } else { '' }
|
|
|
|
if ($item.Present) {
|
|
Write-Host (" [{0}] {1,-44} {2}{3}" -f $SYM_DONE, $item.Label, $tag, $expiry) -ForegroundColor Green
|
|
Add-LogLine ("[+] $($item.Label) — přítomen$expiry")
|
|
} elseif ($item.Required) {
|
|
Write-Host (" [{0}] {1,-44} {2}" -f $SYM_FAIL, $item.Label, $tag) -ForegroundColor Red
|
|
Add-LogLine ("[!] $($item.Label) — CHYBÍ (povinný)")
|
|
} else {
|
|
Write-Host (" [{0}] {1,-44} {2}" -f $SYM_PENDING, $item.Label, $tag) -ForegroundColor DarkGray
|
|
Add-LogLine ("[ ] $($item.Label) — chybí (volitelný)")
|
|
}
|
|
}
|
|
}
|
|
|
|
function Show-Registry {
|
|
param($Reg)
|
|
Write-Head 'REGISTRY'
|
|
|
|
$auText = Get-AvailableUpdatesText $Reg.AvailableUpdates
|
|
$auVal = if ($null -ne $Reg.AvailableUpdates) { [int]$Reg.AvailableUpdates } else { -1 }
|
|
$auColor = if ($auVal -eq 0 -or $auVal -eq 0x4000) { 'Green' } else { 'Cyan' }
|
|
|
|
$stText = $Reg.UEFICA2023StatusText
|
|
$stColor = switch ($stText) {
|
|
'Updated' { 'Green' }
|
|
'Failed' { 'Red' }
|
|
'KeyNotPresent' { 'DarkGray' }
|
|
default { 'White' }
|
|
}
|
|
|
|
$errRaw = $Reg.UEFICA2023Error
|
|
$errOk = ($null -eq $errRaw) -or ([int]$errRaw -eq 0)
|
|
$errText = if ($errOk) { '(žádná)' } else { [string]$errRaw }
|
|
$errColor = if ($errOk) { 'DarkGray' } else { 'Red' }
|
|
|
|
$capText = $Reg.WindowsUEFICA2023CapableText
|
|
$capColor = if ($Reg.WindowsUEFICA2023Capable -eq 2) { 'Green' } else { 'White' }
|
|
|
|
Write-KV 'AvailableUpdates' $auText $auColor
|
|
Write-KV 'UEFICA2023Status' $stText $stColor
|
|
Write-KV 'UEFICA2023Error' $errText $errColor
|
|
Write-KV 'WindowsUEFICA2023Capable' $capText $capColor
|
|
}
|
|
|
|
function Show-Events {
|
|
param($Evt)
|
|
Write-Head 'UDÁLOSTI (System log — Secure Boot)'
|
|
if (-not $Evt.RelevantEvents -or $Evt.RelevantEvents.Count -eq 0) {
|
|
Write-Host ' (žádné relevantní události)' -ForegroundColor DarkGray
|
|
Add-LogLine 'Události: žádné relevantní'
|
|
return
|
|
}
|
|
foreach ($ev in $Evt.RelevantEvents) {
|
|
$evDesc = if ($EVENT_DESC.ContainsKey($ev.EventId)) { $EVENT_DESC[$ev.EventId] } else { $ev.Level }
|
|
$color = if ($ev.EventId -in @(1795,1796)) { 'Red' } `
|
|
elseif ($ev.EventId -eq 1808) { 'Green' } `
|
|
else { 'DarkGray' }
|
|
Write-Host (" {0} EventID {1,-4} {2}" -f $ev.TimeCreated, $ev.EventId, $evDesc) -ForegroundColor $color
|
|
Add-LogLine (" [{0}] EventID {1}: {2}" -f $ev.TimeCreated, $ev.EventId, $evDesc)
|
|
}
|
|
}
|
|
|
|
function Show-Status {
|
|
# Zobrazí všechny 4 sekce + celkový stav.
|
|
param($R, $Prereqs)
|
|
Show-Prerequisites -Prereqs $Prereqs
|
|
Show-Certificates -Cert $R.Certificates
|
|
Show-Registry -Reg $R.Registry
|
|
Show-Events -Evt $R.EventLog
|
|
Write-Host ''
|
|
Write-Host ' STAV: ' -ForegroundColor Gray -NoNewline
|
|
Write-Host $R.CategoryLabel -ForegroundColor $R.CategoryColor
|
|
Add-LogLine ("STAV: {0}" -f $R.CategoryLabel)
|
|
}
|
|
|
|
function Show-TwoRestartBlock {
|
|
param($R)
|
|
$bl = $R.BitLocker
|
|
$blActive = $bl -and ($bl.Status -eq 'On' -or $bl.Status -eq '1' -or $bl.Status -eq '2')
|
|
Write-Host ''
|
|
Write-Host ' *** Boot Manager 2023 je připraven na disku (staged) ***' -ForegroundColor Yellow
|
|
Write-Host ''
|
|
Write-Host ' Pro dokončení jsou potřeba 2 kroky:' -ForegroundColor White
|
|
Write-Host ' 1. RESTART — aktivuje nový Boot Manager' -ForegroundColor Gray
|
|
Write-Host ' 2. Windows automaticky dokončí zbývající kroky po restartu' -ForegroundColor Gray
|
|
Write-Host ''
|
|
if ($blActive -and $bl.UsesPcr) {
|
|
Write-Host ' BitLocker: Disk je chráněn s TPM+PCR7.' -ForegroundColor Yellow
|
|
Write-Host ' Před restartem si připravte BitLocker recovery key!' -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host ' BitLocker: Před restartem ověřte dostupnost recovery key.' -ForegroundColor Gray
|
|
}
|
|
Add-LogLine 'AU=0x4100 — zobrazen blok "2 kroky"'
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Souhlas + restart ──────────────────────────────────────────────────
|
|
|
|
function Get-UserConsent {
|
|
param([string]$Question, [string]$Detail)
|
|
if ($AssumeYes) {
|
|
Write-Line (" {0} -> automaticky ANO (-AssumeYes)" -f $Question) Cyan
|
|
return $true
|
|
}
|
|
if (-not [Environment]::UserInteractive) {
|
|
Write-Line ' Neinteraktivní relace bez -AssumeYes — remediace se neprovede.' Yellow
|
|
return $false
|
|
}
|
|
Write-Host ''
|
|
Write-Host $Question -ForegroundColor White
|
|
if ($Detail) { Write-Host $Detail -ForegroundColor DarkGray }
|
|
try {
|
|
$yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Ano', 'Aplikovat (bez restartu)'
|
|
$no = New-Object System.Management.Automation.Host.ChoiceDescription '&Ne', 'Neprovádět změny'
|
|
return ($Host.UI.PromptForChoice('', 'Vaše volba:', [System.Management.Automation.Host.ChoiceDescription[]]@($yes,$no), 1) -eq 0)
|
|
} catch {
|
|
return ((Read-Host 'Aplikovat? [a/N]') -match '^(a|ano|y|yes)$')
|
|
}
|
|
}
|
|
|
|
function Invoke-RestartOffer {
|
|
param([bool]$Auto)
|
|
if ($Auto) {
|
|
Write-Host ''
|
|
Write-Host ' AutoRestart: restartuji server nyní...' -ForegroundColor Yellow
|
|
Add-LogLine 'Restart: -AutoRestart — spouštím Restart-Computer'
|
|
Flush-Log
|
|
Restart-Computer -Force
|
|
return
|
|
}
|
|
if (-not [Environment]::UserInteractive) {
|
|
Write-Line ' (Neinteraktivní relace — restart proveďte ručně.)' DarkGray
|
|
return
|
|
}
|
|
Write-Host ''
|
|
Write-Host ' Chcete nyní restartovat server? (výchozí: Ne)' -ForegroundColor White
|
|
try {
|
|
$yn = New-Object System.Management.Automation.Host.ChoiceDescription '&Ano', 'Restartovat server nyní'
|
|
$nn = New-Object System.Management.Automation.Host.ChoiceDescription '&Ne', 'Restartovat ručně'
|
|
if ($Host.UI.PromptForChoice('', ' Restart:', [System.Management.Automation.Host.ChoiceDescription[]]@($yn,$nn), 1) -eq 0) {
|
|
Add-LogLine 'Restart: uživatel potvrdil — spouštím Restart-Computer'
|
|
Flush-Log
|
|
Restart-Computer -Force
|
|
} else {
|
|
Write-Host ' Restart odložen — restartujte server ručně co nejdříve.' -ForegroundColor Gray
|
|
Add-LogLine 'Restart: uživatel odložil restart'
|
|
}
|
|
} catch {
|
|
if ((Read-Host ' Restartovat nyní? [a/N]') -match '^(a|ano|y|yes)$') {
|
|
Add-LogLine 'Restart: uživatel potvrdil restart (fallback Read-Host)'
|
|
Flush-Log
|
|
Restart-Computer -Force
|
|
} else {
|
|
Write-Line ' Restart odložen — restartujte server ručně co nejdříve.' Gray
|
|
Add-LogLine 'Restart: uživatel odložil restart (fallback)'
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Předpoklady ────────────────────────────────────────────────────────
|
|
|
|
function Get-Prerequisites {
|
|
param($R, [bool]$IsAdmin)
|
|
$sb = $R.SecureBoot
|
|
$list = @()
|
|
|
|
# 1. Administrátorská práva
|
|
$list += @{
|
|
Label = 'Administrátorská práva'
|
|
Ok = $IsAdmin; Hard = $true
|
|
Note = if (-not $IsAdmin) { 'Spusťte PowerShell "Run as administrator"' } else { $null }
|
|
}
|
|
|
|
# 2. Windows Server
|
|
$isServer = $R.OSCaption -like '*Windows Server*'
|
|
$list += @{
|
|
Label = ("Windows Server (2016/2019/2022/2025) — Zjištěno: {0} (build {1})" -f $R.OSCaption, $R.OSBuildFull)
|
|
Ok = $isServer; Hard = $true
|
|
Note = if (-not $isServer) { 'Skript je určen pro Windows Server' } else { $null }
|
|
}
|
|
|
|
# 3. UEFI + Secure Boot zapnutý
|
|
$sbAllOk = $sb.IsUEFI -and $sb.IsSupported -and $sb.IsEnabled
|
|
$sbNote = if (-not $sb.IsUEFI) { 'Legacy BIOS — Secure Boot není k dispozici' } `
|
|
elseif (-not $sb.IsSupported) { 'Secure Boot není podporováno' } `
|
|
elseif (-not $sb.IsEnabled) { 'Secure Boot je vypnutý — zapněte v UEFI/BIOS' } `
|
|
else { $null }
|
|
$list += @{
|
|
Label = 'UEFI + Secure Boot zapnutý'
|
|
Ok = $sbAllOk; Hard = $true; Note = $sbNote
|
|
}
|
|
|
|
# 4. Stavové registry klíče přítomny (servicing klíč = záplata >= 10/2025)
|
|
$servNote = if (-not $R.Registry.ServicingKeyExists) {
|
|
"build $($R.OSBuildFull) — nainstalujte kumulativní update min. z 10/2025 (KB5066835)"
|
|
} else { $null }
|
|
$list += @{
|
|
Label = 'Úroveň záplat >= 10/2025 — stavové registry klíče přítomny'
|
|
Ok = $R.Registry.ServicingKeyExists; Hard = $true; Note = $servNote
|
|
}
|
|
|
|
# 5. Scheduled task Secure-Boot-Update
|
|
$taskNote = if (-not $R.TaskExists) { "Task '$TASK_NAME' chybí — nainstalujte kumulativní update min. z 10/2025" } else { $null }
|
|
$list += @{
|
|
Label = ("Scheduled task '$TASK_NAME' k dispozici")
|
|
Ok = [bool]$R.TaskExists; Hard = $true; Note = $taskNote
|
|
}
|
|
|
|
# 6. PowerShell SecureBoot cmdlety
|
|
$sbCmdletOk = $null -ne (Get-Command Get-SecureBootUEFI -ErrorAction SilentlyContinue)
|
|
$list += @{
|
|
Label = 'PowerShell SecureBoot cmdlety — Get-SecureBootUEFI dostupný'
|
|
Ok = $sbCmdletOk; Hard = $true
|
|
Note = if (-not $sbCmdletOk) { 'Cmdlet nedostupný — možná Legacy BIOS nebo chybí modul' } else { $null }
|
|
}
|
|
|
|
return $list
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Remediace ──────────────────────────────────────────────────────────
|
|
|
|
function Invoke-Remediation {
|
|
$out = [ordered]@{ Status='Unknown'; Message=''; After=$null }
|
|
Write-Head 'REMEDIACE'
|
|
Add-LogLine 'REMEDIACE START'
|
|
|
|
# Krok 1/3: Nastavit registry
|
|
# AvailableUpdates = 0x5944 pouze jednou — při prvním spuštění (null) nebo po dokončení (0).
|
|
# Systém hodnotu sám spravuje: 0x5944→0x5904→0x5104→0x4104→0x4100→0x4000→0x0.
|
|
$auCurrent = (Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue).AvailableUpdates
|
|
$auNeedsInit = ($null -eq $auCurrent) -or ([int]$auCurrent -eq 0)
|
|
$auInitLabel = if ($auNeedsInit) { '0x5944 (první inicializace)' } else { '0x{0:X} (zachováno, probíhá)' -f [int]$auCurrent }
|
|
|
|
Write-Host (" [1/3] Nastavuji registry (AvailableUpdates={0}) ... " -f $auInitLabel) -NoNewline
|
|
try {
|
|
if (-not (Test-Path $REG_SECUREBOOT)) {
|
|
New-Item -Path $REG_SECUREBOOT -Force -ErrorAction Stop | Out-Null
|
|
}
|
|
Set-ItemProperty -Path $REG_SECUREBOOT -Name 'MicrosoftUpdateManagedOptIn' -Value 1 -Type DWord -Force -ErrorAction Stop
|
|
|
|
if ($auNeedsInit) {
|
|
Set-ItemProperty -Path $REG_SECUREBOOT -Name 'AvailableUpdates' -Value $AVAILABLE_UPDATES_VALUE -Type DWord -Force -ErrorAction Stop
|
|
$auCheck = (Get-ItemProperty $REG_SECUREBOOT -ErrorAction Stop).AvailableUpdates
|
|
if ([int]$auCheck -ne $AVAILABLE_UPDATES_VALUE) {
|
|
throw ('Ověření selhalo — AvailableUpdates=0x{0:X}, očekáváno 0x5944' -f [int]$auCheck)
|
|
}
|
|
}
|
|
|
|
$optOut = Get-ItemProperty $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -ErrorAction SilentlyContinue
|
|
if ($optOut -and $optOut.HighConfidenceOptOut -ne 0) {
|
|
Set-ItemProperty -Path $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -Value 0 -Type DWord -Force -ErrorAction Stop
|
|
}
|
|
|
|
if (-not (Test-Path $REG_SERVICING)) {
|
|
New-Item -Path $REG_SERVICING -Force -ErrorAction Stop | Out-Null
|
|
}
|
|
|
|
Write-Host 'OK' -ForegroundColor Green
|
|
Add-LogLine ("Krok 1: MicrosoftUpdateManagedOptIn=1, AvailableUpdates={0}" -f $auInitLabel)
|
|
} catch {
|
|
Write-Host 'CHYBA' -ForegroundColor Red
|
|
Write-Line (" $($_.Exception.Message)") Red
|
|
$out.Status = 'Error'
|
|
$out.Message = "Zápis registry selhal: $($_.Exception.Message)"
|
|
return $out
|
|
}
|
|
|
|
# Krok 2/3: Spustit servicing task a čekat na změnu stavu
|
|
Write-Host ' [2/3] Spouštím servicing task ' -NoNewline
|
|
$task = Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction SilentlyContinue
|
|
if (-not $task) {
|
|
Write-Host '— task nenalezen.' -ForegroundColor Yellow
|
|
Write-Line ' Registry nastavena; Windows zpracuje při servisním běhu.' DarkGray
|
|
Add-LogLine 'Krok 2: task nenalezen — přeskočeno'
|
|
} else {
|
|
$initAU = [int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates)
|
|
try {
|
|
Start-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction Stop
|
|
Start-Sleep -Seconds 2
|
|
$elapsed = 0
|
|
$state = 'Running'
|
|
$changed = $false
|
|
do {
|
|
Start-Sleep -Seconds 3
|
|
$elapsed += 3
|
|
Write-Host '.' -NoNewline -ForegroundColor DarkGray
|
|
$state = (Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction SilentlyContinue).State
|
|
$nowAU = [int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates)
|
|
if ($nowAU -ne $initAU) { $changed = $true; break }
|
|
} while ($state -eq 'Running' -and $elapsed -lt $TASK_TIMEOUT_SEC)
|
|
|
|
$finalAU = [int]((Get-ItemProperty $REG_SECUREBOOT -Name 'AvailableUpdates' -ErrorAction SilentlyContinue).AvailableUpdates)
|
|
if ($changed) {
|
|
Write-Host (" OK ({0}s, AvailableUpdates -> 0x{1:X})" -f $elapsed, $finalAU) -ForegroundColor Green
|
|
} elseif ($elapsed -ge $TASK_TIMEOUT_SEC) {
|
|
Write-Host (" timeout {0}s (stav: {1})" -f $elapsed, $state) -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host (" OK ({0}s, stav: {1})" -f $elapsed, $state) -ForegroundColor Green
|
|
}
|
|
Add-LogLine ("Krok 2: elapsed={0}s, stav={1}, AvailableUpdates=0x{2:X}" -f $elapsed, $state, $finalAU)
|
|
} catch {
|
|
Write-Host '— nepodařilo se spustit.' -ForegroundColor Yellow
|
|
Write-Line (" $($_.Exception.Message)") DarkGray
|
|
}
|
|
}
|
|
|
|
# Krok 3/3: Ověřit nový stav
|
|
Write-Host ' [3/3] Ověřuji nový stav ... ' -NoNewline
|
|
Start-Sleep -Seconds 2
|
|
$after = Invoke-Detection
|
|
Write-Host 'OK' -ForegroundColor Green
|
|
|
|
$out.Status = 'Applied'
|
|
$out.After = $after
|
|
$out.Message = 'Registry nastavena, task zpracován.'
|
|
Add-LogLine 'REMEDIACE KONEC: Applied'
|
|
return $out
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Stav napříč restarty ───────────────────────────────────────────────
|
|
|
|
function Save-ResumeState {
|
|
param($R, [int]$Cycle)
|
|
try {
|
|
if (-not (Test-Path $WORK_ROOT)) { New-Item -Path $WORK_ROOT -ItemType Directory -Force | Out-Null }
|
|
@{
|
|
ComputerName = $env:COMPUTERNAME
|
|
Timestamp = (Get-Date -Format 'yyyy-MM-dd HH:mm')
|
|
Cycle = $Cycle
|
|
Category = $R.Category
|
|
AvailableUpdates = ('0x{0:X}' -f [int]$R.Registry.AvailableUpdates)
|
|
} | ConvertTo-Json | Set-Content -LiteralPath $STATE_FILE -Encoding UTF8
|
|
} catch { }
|
|
}
|
|
|
|
function Get-ResumeState {
|
|
if (-not (Test-Path $STATE_FILE)) { return $null }
|
|
try { return (Get-Content -LiteralPath $STATE_FILE -Raw | ConvertFrom-Json) } catch { return $null }
|
|
}
|
|
|
|
function Clear-ResumeState {
|
|
if (Test-Path $STATE_FILE) { Remove-Item -LiteralPath $STATE_FILE -Force -ErrorAction SilentlyContinue }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── Main ───────────────────────────────────────────────────────────────
|
|
|
|
$isWhatIf = [bool]$WhatIfPreference
|
|
|
|
# Hlavička
|
|
Write-Host ''
|
|
Write-Rule 'Cyan'
|
|
Write-Host ' SECURE BOOT — KONTROLA A REMEDIACE CERTIFIKÁTŮ' -ForegroundColor White
|
|
Write-Host (" {0,-40} {1}" -f $env:COMPUTERNAME, (Get-Date -Format 'yyyy-MM-dd HH:mm')) -ForegroundColor DarkGray
|
|
Write-Rule 'Cyan'
|
|
|
|
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
|
|
Add-LogLine ('===== SPUŠTĚNO {0} | {1} | admin={2} | CheckOnly={3} AssumeYes={4} Force={5} AutoRestart={6} =====' -f `
|
|
$env:COMPUTERNAME, (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $isAdmin, `
|
|
$CheckOnly.IsPresent, $AssumeYes.IsPresent, $Force.IsPresent, $AutoRestart.IsPresent)
|
|
|
|
$prev = Get-ResumeState
|
|
if ($prev) {
|
|
Write-Host ''
|
|
Write-Host (" Navazuji na předchozí běh (cyklus #{0}, {1}, stav: {2})." -f `
|
|
$prev.Cycle, $prev.Timestamp, $prev.Category) -ForegroundColor DarkCyan
|
|
}
|
|
|
|
# Detekce stavu
|
|
Write-Host ''
|
|
Write-Host ' Zjišťuji stav serveru...' -ForegroundColor DarkGray
|
|
$result = Invoke-Detection
|
|
$prereqs = Get-Prerequisites -R $result -IsAdmin $isAdmin
|
|
|
|
# Zobrazení stavu (4 sekce: předpoklady, certifikáty, registry, události)
|
|
Show-Status -R $result -Prereqs $prereqs
|
|
|
|
# ── CheckOnly: konec bez akce ─────────────────────────────────────────────────
|
|
if ($CheckOnly) {
|
|
Write-Host ''; Write-Rule
|
|
$exitCode = switch -Wildcard ($result.Category) { 'OK*' { 0 } 'UPDATE_*' { 1 } default { 2 } }
|
|
Write-Host (" CHECK: {0} (exit {1})" -f $result.CategoryLabel, $exitCode) -ForegroundColor $result.CategoryColor
|
|
Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray
|
|
Save-ResumeState -R $result -Cycle $(if ($prev) { [int]$prev.Cycle } else { 0 })
|
|
Flush-Log
|
|
exit $exitCode
|
|
}
|
|
|
|
# ── Ověřit hard prerekvizity ─────────────────────────────────────────────────
|
|
$hardFails = @($prereqs | Where-Object { $_.Hard -and -not $_.Ok })
|
|
if ($hardFails.Count) {
|
|
Write-Host ''
|
|
Write-Host ' Nelze pokračovat — vyřešte výše označené problémy a spusťte skript znovu.' -ForegroundColor Red
|
|
Flush-Log
|
|
exit 2
|
|
}
|
|
|
|
# ── Blokující kategorie ───────────────────────────────────────────────────────
|
|
$blockedMap = @{
|
|
'NO_SECUREBOOT' = 'Secure Boot není podporováno (Legacy BIOS). Dokumentujte server jako výjimku.'
|
|
'NO_SECUREBOOT_VM' = 'Secure Boot není podporováno (VM bez UEFI nebo vTPM). Dokumentujte jako výjimku.'
|
|
'SECUREBOOT_DISABLED' = 'Secure Boot je vypnutý. Zapnutí je mimo rozsah tohoto skriptu — nastavte v UEFI/BIOS.'
|
|
'SETUP_MODE' = 'Secure Boot je v Setup Mode. V UEFI/BIOS obnovte Secure Boot klíče (enroll PK) a spusťte skript znovu.'
|
|
'NOT_SUPPORTED' = 'Zařízení nepodporuje automatickou aktualizaci (ConfidenceLevel: Not Supported). Kontaktujte výrobce firmware.'
|
|
'BUILD_OUTDATED' = 'Tento build Windows neumí nasadit 2023 certifikáty. Nainstalujte Windows Update (min. 10/2025, KB5066835) a restartujte.'
|
|
}
|
|
if ($blockedMap.ContainsKey($result.Category)) {
|
|
Write-Host ''
|
|
Write-Host (" {0}" -f $blockedMap[$result.Category]) -ForegroundColor Yellow
|
|
Save-ResumeState -R $result -Cycle $(if ($prev) { [int]$prev.Cycle } else { 0 })
|
|
Flush-Log
|
|
exit 2
|
|
}
|
|
if ($result.Category -eq 'FIRMWARE_UPDATE_NEEDED') {
|
|
Write-Host ''
|
|
Write-Host (" Aktualizace pozastavena — firmware serveru {0} ji nepodporuje." -f $result.Hardware.Manufacturer) -ForegroundColor Yellow
|
|
Write-Host ' Aktualizujte firmware u výrobce a poté spusťte skript znovu.' -ForegroundColor Gray
|
|
Save-ResumeState -R $result -Cycle $(if ($prev) { [int]$prev.Cycle } else { 0 })
|
|
Flush-Log
|
|
exit 2
|
|
}
|
|
|
|
# ── UPDATE_FAILED — upozornit, ale přesto nabídnout opakování ────────────────
|
|
if ($result.Category -eq 'UPDATE_FAILED') {
|
|
Write-Host ''
|
|
$errEvtId = $result.Registry.UEFICA2023ErrorEvent
|
|
if ($errEvtId -and [int]$errEvtId -ne 0) {
|
|
Write-Host (" Předchozí pokus selhal (UEFICA2023ErrorEvent={0}). Zkuste znovu nebo aktualizujte firmware." -f $errEvtId) -ForegroundColor Red
|
|
} elseif ($result.EventLog.ById[1795]) {
|
|
Write-Host (" Selhání firmwaru (EventID 1795, {0}). Aktualizujte firmware nebo Hyper-V hostitele (KB5085790)." -f $result.EventLog.ById[1795].Time) -ForegroundColor Red
|
|
} else {
|
|
Write-Host ' Předchozí pokus selhal. Zkuste spustit remediaci znovu.' -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
$cycle = if ($prev) { [int]$prev.Cycle } else { 0 }
|
|
|
|
# ── Předběžná kontrola: povinné 2023 certy přítomny? ─────────────────────────
|
|
# Pokud ano a není -Force → ukončit bez remediace.
|
|
$certsReady = $result.Certificates.KEK.Has2023 -and $result.Certificates.DB.Has2023WindowsUEFI
|
|
if ($certsReady -and ($result.Category -like 'OK*') -and -not $Force) {
|
|
Write-Host ''
|
|
Write-Host ' Povinné 2023 certifikáty (Windows UEFI CA 2023 + KEK 2K CA 2023) jsou' -ForegroundColor Green
|
|
Write-Host ' již přítomny v UEFI databázích. Na tomto serveru není nutná žádná akce.' -ForegroundColor Green
|
|
Write-Host ' (Tip: parametr -Force vynutí remediaci i v tomto stavu.)' -ForegroundColor DarkGray
|
|
Write-Host ''; Write-Rule 'Cyan'
|
|
Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray
|
|
Write-Rule 'Cyan'; Write-Host ''
|
|
if ($result.Category -like 'OK*') { Clear-ResumeState } else { Save-ResumeState -R $result -Cycle $cycle }
|
|
Flush-Log
|
|
exit 0
|
|
}
|
|
|
|
if ($certsReady -and $Force) {
|
|
Write-Host ''
|
|
Write-Host ' ! Certifikáty jsou přítomny, ale -Force umožňuje spustit remediaci znovu.' -ForegroundColor Yellow
|
|
}
|
|
|
|
# ── AU=0x4100: Boot Manager staged, stačí RESTART ────────────────────────────
|
|
$auNow = [int]$result.Registry.AvailableUpdates
|
|
if ($auNow -eq 0x4100) {
|
|
Show-TwoRestartBlock -R $result
|
|
if (-not $isWhatIf) {
|
|
Invoke-RestartOffer -Auto:$AutoRestart.IsPresent
|
|
}
|
|
Write-Host ''; Write-Rule 'Cyan'
|
|
Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray
|
|
Write-Rule 'Cyan'; Write-Host ''
|
|
Save-ResumeState -R $result -Cycle $cycle
|
|
Flush-Log
|
|
exit 1
|
|
}
|
|
|
|
# ── Zkontrolovat administrátorská práva před remediací ────────────────────────
|
|
if (-not $isAdmin) {
|
|
Write-Host ''
|
|
Write-Host ' Remediaci nelze provést bez práv Administrator.' -ForegroundColor Yellow
|
|
Write-Host ' Spusťte PowerShell "Run as administrator" a zkuste znovu.' -ForegroundColor Gray
|
|
Flush-Log
|
|
exit 2
|
|
}
|
|
|
|
# ── WhatIf: zobrazit co by se stalo ─────────────────────────────────────────
|
|
$remediation = $null
|
|
if ($isWhatIf) {
|
|
Write-Host ''
|
|
Write-Host ' -WhatIf — co by remediace udělala (bez změn):' -ForegroundColor Cyan
|
|
Write-Host ' - Nastaví MicrosoftUpdateManagedOptIn=1, HighConfidenceOptOut=0' -ForegroundColor Gray
|
|
$auWhatIfLabel = if ($auNow -eq 0) { '0x5944 (první inicializace)' } else { '0x{0:X} (zachováno)' -f $auNow }
|
|
Write-Host (" - AvailableUpdates: {0}" -f $auWhatIfLabel) -ForegroundColor Gray
|
|
Write-Host ' - Spustí servicing task a bude čekat na změnu stavu' -ForegroundColor Gray
|
|
$autoRestartNote = if ($AutoRestart) { 'ANO (-AutoRestart)' } else { 'NE (nabídne dotaz)' }
|
|
Write-Host (" - Restart serveru: {0}" -f $autoRestartNote) -ForegroundColor Gray
|
|
} else {
|
|
# ── Nabídnout remediaci ──────────────────────────────────────────────────
|
|
$question = switch ($result.Category) {
|
|
'UPDATE_NEEDED' { 'Server potřebuje aktualizaci Secure Boot certifikátů. Chcete zahájit proces?' }
|
|
'UPDATE_PARTIAL' { 'Aktualizace probíhá — část certifikátů je nasazena. Spustit task pro posun procesu?' }
|
|
'UPDATE_PENDING_RESTART' { 'Certifikáty jsou v procesu. Spustit servicing task?' }
|
|
'UPDATE_FAILED' { 'Předchozí pokus selhal. Chcete zkusit remediaci znovu?' }
|
|
default { 'Chcete spustit remediaci Secure Boot certifikátů?' }
|
|
}
|
|
$detail = 'Nastaví registry (KB5068202) a spustí servicing task. Server NEBUDE restartován (bez -AutoRestart).'
|
|
|
|
if (Get-UserConsent -Question $question -Detail $detail) {
|
|
$cycle++
|
|
Add-LogLine ("Cyklus #{0} | {1} | kategorie={2}" -f $cycle, $result.Hostname, $result.Category)
|
|
$remediation = Invoke-Remediation
|
|
Flush-Log
|
|
} else {
|
|
Write-Host ''
|
|
Write-Host ' Remediace neprovedena — spusťte skript znovu, až budete připraveni.' -ForegroundColor Yellow
|
|
}
|
|
}
|
|
|
|
# ── Závěr ─────────────────────────────────────────────────────────────────────
|
|
Write-Host ''; Write-Rule 'Cyan'
|
|
|
|
if ($remediation -and $remediation.Status -eq 'Applied') {
|
|
$after = $remediation.After
|
|
$afterAU = [int]$after.Registry.AvailableUpdates
|
|
$afterDone = Test-RemediationComplete -R $after
|
|
|
|
Write-Host ' Stav po remediaci:' -ForegroundColor Cyan
|
|
Show-Certificates -Cert $after.Certificates
|
|
Show-Registry -Reg $after.Registry
|
|
|
|
Write-Host ''
|
|
if ($afterDone) {
|
|
Write-Host (" [{0}] HOTOVO — Secure Boot certifikáty 2023 úspěšně nasazeny!" -f $SYM_DONE) -ForegroundColor Green
|
|
Write-Host ' Podmínky splněny: certifikáty v DB/KEK, UEFICA2023Status=Updated, AvailableUpdates=0x0/0x4000.' -ForegroundColor Green
|
|
Clear-ResumeState
|
|
} elseif ($afterAU -eq 0x4100) {
|
|
Show-TwoRestartBlock -R $after
|
|
Invoke-RestartOffer -Auto:$AutoRestart.IsPresent
|
|
Save-ResumeState -R $after -Cycle $cycle
|
|
} else {
|
|
Write-Host ' Remediace proběhla — proces vyžaduje restart pro dokončení.' -ForegroundColor Yellow
|
|
Write-Host ''
|
|
Write-Host ' Další kroky:' -ForegroundColor White
|
|
Write-Host ' 1. Restartujte server.' -ForegroundColor Gray
|
|
Write-Host ' 2. Spusťte skript znovu — opakujte, dokud nebude HOTOVO.' -ForegroundColor Gray
|
|
Write-Host (" 3. Cíl: AvailableUpdates=0x0, UEFICA2023Status=Updated, certifikáty v KEK+DB.") -ForegroundColor DarkGray
|
|
Save-ResumeState -R $after -Cycle $cycle
|
|
}
|
|
} elseif ($remediation -and $remediation.Status -eq 'Error') {
|
|
Write-Host (" CHYBA REMEDIACE — {0}" -f $remediation.Message) -ForegroundColor Red
|
|
Save-ResumeState -R $result -Cycle $cycle
|
|
} else {
|
|
# Bez remediace (odmítnutí / WhatIf / blocked)
|
|
Write-Host ' KONEC — bez změn na serveru.' -ForegroundColor Cyan
|
|
$finalR = $result
|
|
if ($finalR.Category -like 'OK*') { Clear-ResumeState } else { Save-ResumeState -R $finalR -Cycle $cycle }
|
|
}
|
|
|
|
Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray
|
|
Write-Rule 'Cyan'; Write-Host ''
|
|
Flush-Log
|
|
|
|
#endregion
|