From 2712030aeaa750e815d34e3a77d74ed642fb32d8 Mon Sep 17 00:00:00 2001 From: Petr Stepan Date: Mon, 8 Jun 2026 15:38:04 +0200 Subject: [PATCH] Clean repo --- Invoke-SecureBootAudit.ps1 | 679 ---------------------------- Invoke-SecureBootRemediation.ps1 | 38 ++ README.md | 164 ++++--- Set-SecureBootCertificateUpdate.ps1 | 519 --------------------- 4 files changed, 141 insertions(+), 1259 deletions(-) delete mode 100644 Invoke-SecureBootAudit.ps1 delete mode 100644 Set-SecureBootCertificateUpdate.ps1 diff --git a/Invoke-SecureBootAudit.ps1 b/Invoke-SecureBootAudit.ps1 deleted file mode 100644 index 97dedfd..0000000 --- a/Invoke-SecureBootAudit.ps1 +++ /dev/null @@ -1,679 +0,0 @@ -#Requires -Version 5.1 -<# -.SYNOPSIS - Audituje stav Secure Boot certifikátů na Windows Serveru. - -.DESCRIPTION - Detekuje podporu Secure Boot, stav povolení, přítomnost certifikátů (expirující 2011 - vs. nové 2023 náhrady), stav v registrech a relevantní záznamy v Event Logu. - Vrací strukturovaný JSON výstup + human-readable souhrn. - - Skript nevyžaduje žádné externí moduly (pure PowerShell 5.1+). - Toleruje prostředí bez Secure Boot (Legacy BIOS, Gen1 VM apod.). - - Spouštění na vzdáleném serveru: - Invoke-Command -ComputerName SERVER01 -FilePath .\Invoke-SecureBootAudit.ps1 - - Spouštění lokálně s uložením výsledku: - .\Invoke-SecureBootAudit.ps1 -OutputPath C:\Audit\result.json - -.PARAMETER OutputPath - Cesta pro uložení JSON výsledku. Pokud není zadána, JSON se nevypisuje na konzoli - (použijte -JsonOnly nebo -PassThru pro programatické zpracování). - -.PARAMETER JsonOnly - Vypíše pouze JSON na stdout, bez human-readable souhrnue. - -.PARAMETER PassThru - Vrátí výsledek jako PowerShell objekt (vhodné pro Invoke-Command pipeline). - -.EXAMPLE - .\Invoke-SecureBootAudit.ps1 - -.EXAMPLE - .\Invoke-SecureBootAudit.ps1 -OutputPath C:\Audit\SERVER01.json - -.EXAMPLE - Invoke-Command -ComputerName SERVER01 -FilePath .\Invoke-SecureBootAudit.ps1 -ArgumentList $null,$false,$true - -.NOTES - Vyžaduje spuštění jako Administrator pro přístup k UEFI databázím a Event Logu. - Relevantní KB: KB5062710, KB5068202, KB5085046 -#> - -[CmdletBinding()] -param( - [string]$OutputPath, - [switch]$JsonOnly, - [switch]$PassThru -) - -$ErrorActionPreference = 'SilentlyContinue' - -#region ── Helper functions ────────────────────────────────────────────────── - -function Get-EnvironmentType { - try { - $cs = Get-WmiObject 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' - SerialNumber = 'Unknown' - FirmwareType = 'Unknown' - BiosVersion = 'Unknown' - BiosReleaseDate = 'Unknown' - } - - try { - $cs = Get-WmiObject Win32_ComputerSystem -ErrorAction Stop - $hw.Manufacturer = [string]$cs.Manufacturer - $hw.Model = [string]$cs.Model - } catch {} - - try { - $bios = Get-WmiObject Win32_BIOS -ErrorAction Stop - $hw.BiosVersion = [string]$bios.SMBIOSBIOSVersion - $hw.SerialNumber = [string]$bios.SerialNumber - if ($bios.ReleaseDate) { - $hw.BiosReleaseDate = [Management.ManagementDateTimeConverter]::ToDateTime( - $bios.ReleaseDate).ToString('yyyy-MM-dd') - } - } catch {} - - # Firmware type — primárně z registry, fallback přes přítomnost SecureBoot klíče - $fwTypeKey = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' ` - -Name PEFirmwareType -ErrorAction SilentlyContinue - if ($fwTypeKey) { - $hw.FirmwareType = if ($fwTypeKey.PEFirmwareType -eq 2) { 'UEFI' } else { 'Legacy BIOS' } - } elseif (Test-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot') { - $hw.FirmwareType = 'UEFI' - } else { - $hw.FirmwareType = 'Legacy BIOS' - } - - return $hw -} - -function Get-SecureBootState { - $state = [ordered]@{ - IsUEFI = $false - IsSupported = $false - IsEnabled = $false - ConfirmResult = 'Unknown' - Error = $null - } - - $fwTypeKey = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' ` - -Name PEFirmwareType -ErrorAction SilentlyContinue - $state.IsUEFI = ($fwTypeKey -and $fwTypeKey.PEFirmwareType -eq 2) ` - -or (Test-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot') - - if (-not $state.IsUEFI) { return $state } - - try { - $result = Confirm-SecureBootUEFI -ErrorAction Stop - $state.IsSupported = $true - $state.IsEnabled = [bool]$result - $state.ConfirmResult = $result.ToString() - } catch { - $msg = $_.Exception.Message - if ($msg -like '*not supported*' -or $msg -like '*Cmdlet not supported*') { - $state.IsSupported = $false - $state.ConfirmResult = 'NotSupported' - } elseif ($msg -like '*disabled*') { - $state.IsSupported = $true - $state.IsEnabled = $false - $state.ConfirmResult = 'Disabled' - } else { - $state.IsSupported = $true - $state.IsEnabled = $false - $state.ConfirmResult = "Error" - $state.Error = $msg - } - } - - return $state -} - -function Parse-EFISignatureList { - <# - .SYNOPSIS - Parsuje EFI_SIGNATURE_LIST strukturu a vrátí X.509 certifikáty. - #> - param([byte[]]$Bytes) - - $certs = @() - if (-not $Bytes -or $Bytes.Length -lt 28) { return $certs } - - # EFI_CERT_X509_GUID {a5c059a1-94e4-4aa7-87b5-ab155c2bf072} v little-endian - $X509_GUID = [byte[]]( - 0xa1,0x59,0xc0,0xa5, # Data1 LE - 0xe4,0x94, # Data2 LE - 0xa7,0x4a, # Data3 LE - 0x87,0xb5,0xab,0x15,0x5c,0x2b,0xf0,0x72 # Data4 BE - ) - - $offset = 0 - while ($offset + 28 -le $Bytes.Length) { - $sigTypeGUID = $Bytes[$offset..($offset + 15)] - $sigListSize = [BitConverter]::ToUInt32($Bytes, $offset + 16) - $sigHeaderSize = [BitConverter]::ToUInt32($Bytes, $offset + 20) - $sigSize = [BitConverter]::ToUInt32($Bytes, $offset + 24) - - if ($sigListSize -lt 28 -or $sigListSize -gt ($Bytes.Length - $offset)) { break } - - # Porovnat GUID - $isX509 = $true - for ($i = 0; $i -lt 16; $i++) { - if ($sigTypeGUID[$i] -ne $X509_GUID[$i]) { $isX509 = $false; break } - } - - if ($isX509 -and $sigSize -gt 16) { - $sigOffset = $offset + 28 + $sigHeaderSize - $listEnd = $offset + $sigListSize - - while ($sigOffset + $sigSize -le $listEnd) { - # Přeskočit 16 B SignatureOwner GUID, zbytek je DER certifikát - $certOffset = $sigOffset + 16 - $certSize = [int]$sigSize - 16 - - if ($certOffset + $certSize -le $Bytes.Length -and $certSize -gt 0) { - $certBytes = $Bytes[$certOffset..($certOffset + $certSize - 1)] - try { - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2( - , [byte[]]$certBytes) - $certs += $cert - } catch { } - } - $sigOffset += $sigSize - } - } - - $offset += $sigListSize - } - - return $certs -} - -function Convert-CertToInfo { - param([System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert) - return [ordered]@{ - Subject = $Cert.Subject - Thumbprint = $Cert.Thumbprint - NotBefore = $Cert.NotBefore.ToString('yyyy-MM-dd') - NotAfter = $Cert.NotAfter.ToString('yyyy-MM-dd') - } -} - -function Get-CertificateStatus { - $status = [ordered]@{ - KEK = [ordered]@{ - Has2011 = $false - Has2023 = $false - Certs2011 = @() - Certs2023 = @() - Error = $null - } - DB = [ordered]@{ - Has2011UEFI = $false - Has2011WindowsPCA = $false - Has2023UEFI = $false - Has2023OptionROM = $false - Has2023WindowsUEFI = $false - Certs2011 = @() - Certs2023 = @() - Error = $null - } - AnyExpiring2011 = $false - AllReplacements2023 = $false - AllReplacements2023_VM = $false - } - - # ── KEK databáze ── - try { - $kekObj = Get-SecureBootUEFI -Name KEK -ErrorAction Stop - $kekCerts = Parse-EFISignatureList -Bytes $kekObj.Bytes - - foreach ($cert in $kekCerts) { - $subj = $cert.Subject - $info = Convert-CertToInfo $cert - - if ($subj -like '*KEK CA 2011*') { - $status.KEK.Has2011 = $true - $status.KEK.Certs2011 += $info - } - if ($subj -like '*KEK 2K CA 2023*' -or ($subj -like '*KEK*CA 2023*')) { - $status.KEK.Has2023 = $true - $status.KEK.Certs2023 += $info - } - } - } catch { - $status.KEK.Error = $_.Exception.Message - } - - # ── DB databáze ── - try { - $dbObj = Get-SecureBootUEFI -Name db -ErrorAction Stop - $dbCerts = Parse-EFISignatureList -Bytes $dbObj.Bytes - - foreach ($cert in $dbCerts) { - $subj = $cert.Subject - $info = Convert-CertToInfo $cert - - # 2011 expirující certifikáty - if ($subj -like '*UEFI CA 2011*') { - $status.DB.Has2011UEFI = $true - $status.DB.Certs2011 += $info - } - if ($subj -like '*Windows Production PCA 2011*' -or $subj -like '*Windows PCA 2011*') { - $status.DB.Has2011WindowsPCA = $true - $status.DB.Certs2011 += $info - } - - # 2023 náhradní certifikáty - if ($subj -like '*UEFI CA 2023*' -and - $subj -notlike '*Option ROM*' -and - $subj -notlike '*Windows UEFI*') { - $status.DB.Has2023UEFI = $true - $status.DB.Certs2023 += $info - } - if ($subj -like '*Option ROM UEFI CA 2023*') { - $status.DB.Has2023OptionROM = $true - $status.DB.Certs2023 += $info - } - if ($subj -like '*Windows UEFI CA 2023*') { - $status.DB.Has2023WindowsUEFI = $true - $status.DB.Certs2023 += $info - } - } - } catch { - $status.DB.Error = $_.Exception.Message - } - - $status.AnyExpiring2011 = $status.KEK.Has2011 -or - $status.DB.Has2011UEFI -or - $status.DB.Has2011WindowsPCA - - # Plná sada pro fyzické servery (všechny 4 certifikáty) - $status.AllReplacements2023 = $status.KEK.Has2023 -and - $status.DB.Has2023UEFI -and - $status.DB.Has2023OptionROM -and - $status.DB.Has2023WindowsUEFI - - # Minimální sada pro VM (UEFI CA 2023 + Option ROM nejsou na VM povinné — - # VM nemá fyzické PCIe option ROM karty, hypervisor řídí UEFI obsah) - $status.AllReplacements2023_VM = $status.KEK.Has2023 -and - $status.DB.Has2023WindowsUEFI - - return $status -} - -function Get-RegistryStatus { - $reg = [ordered]@{ - SecureBootEnabled = $null - AvailableUpdates = $null - HighConfidenceOptOut = $null - ServicingKeyExists = $false - UEFICA2023Status = $null - UEFICA2023StatusText = 'KeyNotPresent' - UEFICA2023Error = $null - WindowsUEFICA2023Capable = $null - } - - # Hlavní klíč - $mainProps = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot' ` - -ErrorAction SilentlyContinue - if ($mainProps) { - $reg.SecureBootEnabled = $mainProps.SecureBootEnabled - $reg.AvailableUpdates = $mainProps.AvailableUpdates - $reg.HighConfidenceOptOut = $mainProps.HighConfidenceOptOut - } - - # Servicing podklíč - $svcProps = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing' ` - -ErrorAction SilentlyContinue - if ($svcProps) { - $reg.ServicingKeyExists = $true - $reg.UEFICA2023Status = $svcProps.UEFICA2023Status - $reg.UEFICA2023Error = $svcProps.UEFICA2023Error - $reg.WindowsUEFICA2023Capable = $svcProps.WindowsUEFICA2023Capable - - $reg.UEFICA2023StatusText = switch ($reg.UEFICA2023Status) { - 0 { 'NotStarted' } - 1 { 'InProgress' } - 2 { 'Success' } - 3 { 'Failed' } - $null { 'KeyNotPresent' } - default { "Unknown ($($reg.UEFICA2023Status))" } - } - } - - return $reg -} - -function Get-EventLogStatus { - $evtStatus = [ordered]@{ - LastEventId = $null - LastEventTime = $null - LastEventMessage = $null - ConfidenceLevel = 'NoRelevantEvents' - RelevantEvents = @() - Error = $null - } - - $relevantIds = @(1795, 1796, 1800, 1801, 1802, 1803, 1808) - - try { - $events = Get-WinEvent -FilterHashtable @{ - LogName = 'System' - Id = $relevantIds - } -MaxEvents 20 -ErrorAction Stop - - if ($events) { - $sorted = $events | Sort-Object TimeCreated -Descending - - foreach ($evt in ($sorted | Select-Object -First 10)) { - $msgShort = ($evt.Message -replace '\s+', ' ').TrimStart() - $msgShort = if ($msgShort.Length -gt 250) { $msgShort.Substring(0, 250) + '...' } else { $msgShort } - - $evtStatus.RelevantEvents += [ordered]@{ - EventId = $evt.Id - TimeCreated = $evt.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') - Level = $evt.LevelDisplayName - Message = $msgShort - } - } - - $last = $sorted | Select-Object -First 1 - $evtStatus.LastEventId = $last.Id - $evtStatus.LastEventTime = $last.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') - $lastMsg = ($last.Message -replace '\s+', ' ').TrimStart() - $evtStatus.LastEventMessage = if ($lastMsg.Length -gt 300) { - $lastMsg.Substring(0, 300) + '...' - } else { $lastMsg } - - $evtStatus.ConfidenceLevel = switch ($last.Id) { - 1808 { 'HighConfidence-Success' } - 1801 { 'HighConfidence-Failed' } - 1795 { 'Failed-HyperVKnownIssue' } - 1802 { 'Pending' } - 1803 { 'InProgress' } - default { 'Informational' } - } - } - } catch { - $msg = $_.Exception.Message - if ($msg -like '*No events*' -or $_.CategoryInfo.Reason -eq 'NoMatchingEventsException') { - $evtStatus.ConfidenceLevel = 'NoRelevantEvents' - } else { - $evtStatus.Error = $msg - $evtStatus.ConfidenceLevel = 'EventLogError' - } - } - - return $evtStatus -} - -function Get-RemediationCategory { - param($Result) - - $sb = $Result.SecureBoot - $cert = $Result.Certificates - $reg = $Result.Registry - $env = $Result.EnvironmentType - - # Bez UEFI / Secure Boot není podporováno - if (-not $sb.IsUEFI -or -not $sb.IsSupported) { - if ($env -like '*VM*') { - return @{ Code = 'NO_SECUREBOOT_VM'; Emoji = '❌'; Label = 'Secure Boot nepodporováno (VM bez vTPM/UEFI)' } - } - return @{ Code = 'NO_SECUREBOOT'; Emoji = '❌'; Label = 'Secure Boot nepodporováno (Legacy BIOS)' } - } - - if (-not $sb.IsEnabled) { - return @{ Code = 'SECUREBOOT_DISABLED'; Emoji = '⏸️'; Label = 'Secure Boot vypnuto' } - } - - # Secure Boot je zapnutý — zkontrolovat certifikáty - # Na VM stačí minimální sada (KEK 2K CA 2023 + Windows UEFI CA 2023), - # UEFI CA 2023 a Option ROM UEFI CA 2023 nejsou na VM povinné - $isVM = $env -like '*VM*' - $certOK = if ($isVM) { $cert.AllReplacements2023_VM } else { $cert.AllReplacements2023 } - $certOKFull = $cert.AllReplacements2023 - - if ($certOK -and -not $cert.AnyExpiring2011) { - $_label = if ($isVM -and -not $certOKFull) { - 'OK — má povinné 2023 certifikáty pro VM (UEFI CA + Option ROM nejsou na VM vyžadovány)' - } else { - 'OK — má nové 2023 certifikáty' - } - return @{ Code = 'OK'; Emoji = '✅'; Label = $_label } - } - if ($certOK -and $cert.AnyExpiring2011) { - $_label = if ($isVM -and -not $certOKFull) { - 'OK — přechodný stav, VM má povinné 2023 certifikáty' - } else { - 'OK — přechodný stav (2023 i 2011 certifikáty)' - } - return @{ Code = 'OK_TRANSITION'; Emoji = '✅'; Label = $_label } - } - - # Selhání aktualizace - if ($reg.UEFICA2023Status -eq 3 -or - $Result.EventLog.LastEventId -eq 1795 -or - $Result.EventLog.LastEventId -eq 1801) { - return @{ Code = 'UPDATE_FAILED'; Emoji = '🔴'; Label = 'Selhání aktualizace certifikátů' } - } - - # Aktualizace právě probíhá / čeká na restart - if ($reg.UEFICA2023Status -eq 1 -or $reg.UEFICA2023Status -eq 2) { - return @{ Code = 'UPDATE_PENDING'; Emoji = '⏳'; Label = 'Aktualizace dokončena, čeká na restart' } - } - - # Firmware nepodporuje nové certifikáty - if ($null -ne $reg.WindowsUEFICA2023Capable -and $reg.WindowsUEFICA2023Capable -eq 0) { - return @{ Code = 'FIRMWARE_UPDATE_NEEDED'; Emoji = '🔧'; Label = 'Čeká na firmware update (OEM)' } - } - - # Nutná aktualizace certifikátů - return @{ Code = 'UPDATE_NEEDED'; Emoji = '⚠️'; Label = 'Nutná aktualizace certifikátů' } -} - -#endregion - -#region ── Main ────────────────────────────────────────────────────────────── - -$auditStart = Get-Date - -# Předpočítat hodnoty, které by způsobily parse error uvnitř hashtable (try/catch v PS 5.1) -$_fqdn = try { [Net.Dns]::GetHostEntry('').HostName } catch { $env:COMPUTERNAME } -$_osInfo = Get-WmiObject Win32_OperatingSystem -ErrorAction SilentlyContinue -$_osCaption = if ($_osInfo) { $_osInfo.Caption } else { $null } -$_osBuild = if ($_osInfo) { $_osInfo.BuildNumber } else { $null } - -$result = [ordered]@{ - AuditTimestamp = $auditStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") - Hostname = $env:COMPUTERNAME - FQDN = $_fqdn - OSCaption = $_osCaption - OSBuild = $_osBuild - Architecture = $env:PROCESSOR_ARCHITECTURE - EnvironmentType = Get-EnvironmentType - Hardware = Get-HardwareInfo - SecureBoot = Get-SecureBootState - Certificates = $null - Registry = Get-RegistryStatus - EventLog = Get-EventLogStatus - Category = $null - CategoryLabel = $null - RemediationAction = $null -} - -# Certifikáty parsovat pouze pokud je Secure Boot dostupný -if ($result.SecureBoot.IsUEFI -and $result.SecureBoot.IsSupported) { - $result.Certificates = Get-CertificateStatus -} else { - $result.Certificates = [ordered]@{ - KEK = [ordered]@{ - Has2011 = $false; Has2023 = $false - Certs2011 = @(); Certs2023 = @() - Error = 'Secure Boot not available — certificate check skipped' - } - DB = [ordered]@{ - Has2011UEFI = $false; Has2011WindowsPCA = $false - Has2023UEFI = $false; Has2023OptionROM = $false; Has2023WindowsUEFI = $false - Certs2011 = @(); Certs2023 = @() - Error = 'Secure Boot not available — certificate check skipped' - } - AnyExpiring2011 = $false - AllReplacements2023 = $false - AllReplacements2023_VM = $false - } -} - -# Kategorie -$cat = Get-RemediationCategory -Result $result -$result.Category = $cat.Code -$result.CategoryLabel = "$($cat.Emoji) $($cat.Label)" - -$remediationMap = @{ - 'OK' = 'Žádná akce' - 'OK_TRANSITION' = 'Žádná akce (monitorovat dokud nezmizí 2011 certifikáty)' - 'UPDATE_NEEDED' = 'Naplánovat rollout nových certifikátů (KB5068202)' - 'UPDATE_FAILED' = 'Troubleshooting selhání — zkontrolovat Event 1795/1801 (KB5085046)' - 'UPDATE_PENDING' = 'Provést restart serveru pro dokončení aktualizace' - 'FIRMWARE_UPDATE_NEEDED'= 'Kontaktovat OEM / aktualizovat firmware serveru' - 'SECUREBOOT_DISABLED' = 'Rozhodnutí o zapnutí Secure Boot (mimo scope skriptu)' - 'NO_SECUREBOOT' = 'Dokumentovat jako výjimku — Legacy BIOS, Secure Boot nelze' - 'NO_SECUREBOOT_VM' = 'Dokumentovat jako výjimku — VM bez Secure Boot (Gen1 / bez vTPM)' -} -$result.RemediationAction = $remediationMap[$result.Category] - -$result.AuditDurationMs = [int](Get-Date).Subtract($auditStart).TotalMilliseconds - -#endregion - -#region ── Output ──────────────────────────────────────────────────────────── - -$jsonOutput = $result | ConvertTo-Json -Depth 10 - -if ($OutputPath) { - $jsonOutput | Out-File -FilePath $OutputPath -Encoding UTF8 -Force - Write-Host "JSON uložen: $OutputPath" -ForegroundColor Green -} - -if (-not $JsonOnly) { - $sb = $result.SecureBoot - $cert = $result.Certificates - $reg = $result.Registry - $evt = $result.EventLog - $hw = $result.Hardware - - $catColor = switch -Wildcard ($result.Category) { - 'OK*' { 'Green' } - 'UPDATE_NEEDED' { 'Yellow' } - 'UPDATE_PENDING' { 'Cyan' } - 'UPDATE_FAILED' { 'Red' } - 'FIRMWARE*' { 'Magenta' } - 'SECUREBOOT_DISABLED' { 'Yellow' } - default { 'Gray' } - } - - $line = '=' * 60 - Write-Host '' - Write-Host $line -ForegroundColor Cyan - Write-Host " SECURE BOOT AUDIT — $($result.Hostname)" -ForegroundColor Cyan - Write-Host " $($result.AuditTimestamp)" -ForegroundColor DarkGray - Write-Host $line -ForegroundColor Cyan - Write-Host '' - - Write-Host 'SERVER' -ForegroundColor Yellow - Write-Host " Hostname : $($result.Hostname)" - Write-Host " FQDN : $($result.FQDN)" - Write-Host " OS : $($result.OSCaption) (Build $($result.OSBuild))" - Write-Host " Prostředí : $($result.EnvironmentType)" - Write-Host " Hardware : $($hw.Manufacturer) $($hw.Model) [S/N: $($hw.SerialNumber)]" - Write-Host " BIOS/FW : $($hw.BiosVersion) ($($hw.BiosReleaseDate))" - Write-Host " Firmware typ: $($hw.FirmwareType)" - Write-Host '' - - Write-Host 'SECURE BOOT' -ForegroundColor Yellow - Write-Host " UEFI firmware : $($sb.IsUEFI)" - Write-Host " Podporováno : $($sb.IsSupported)" - Write-Host " Povoleno : $($sb.IsEnabled)" - if ($sb.Error) { Write-Host " ! Chyba : $($sb.Error)" -ForegroundColor Red } - Write-Host '' - - Write-Host 'CERTIFIKÁTY' -ForegroundColor Yellow - $c = $cert - Write-Host " ── Expirující 2011 ──────────────────────────────────" - Write-Host " KEK CA 2011 (exp. 24.6.2026) : $(if ($c.KEK.Has2011) {'PŘÍTOMEN ⚠️'} else {'chybí'})" - Write-Host " DB UEFI CA 2011 (exp. 27.6.2026): $(if ($c.DB.Has2011UEFI) {'PŘÍTOMEN ⚠️'} else {'chybí'})" - Write-Host " DB Windows PCA 2011 (19.10.2026) : $(if ($c.DB.Has2011WindowsPCA) {'PŘÍTOMEN ⚠️'} else {'chybí'})" - Write-Host " ── Nové náhrady 2023 ────────────────────────────────" - Write-Host " KEK 2K CA 2023 : $(if ($c.KEK.Has2023) {'přítomen ✓'} else {'CHYBÍ'})" - Write-Host " DB UEFI CA 2023 : $(if ($c.DB.Has2023UEFI) {'přítomen ✓'} else {'CHYBÍ'})" - Write-Host " DB Option ROM UEFI CA 2023 : $(if ($c.DB.Has2023OptionROM) {'přítomen ✓'} else {'CHYBÍ'})" - Write-Host " DB Windows UEFI CA 2023 : $(if ($c.DB.Has2023WindowsUEFI) {'přítomen ✓'} else {'CHYBÍ'})" - if ($c.KEK.Error) { Write-Host " ! KEK chyba : $($c.KEK.Error)" -ForegroundColor Red } - if ($c.DB.Error) { Write-Host " ! DB chyba : $($c.DB.Error)" -ForegroundColor Red } - Write-Host '' - - Write-Host 'REGISTRY STAV' -ForegroundColor Yellow - Write-Host " UEFICA2023Status : $($reg.UEFICA2023Status) ($($reg.UEFICA2023StatusText))" - Write-Host " AvailableUpdates : $($reg.AvailableUpdates)" - Write-Host " WindowsUEFICA2023Capable: $($reg.WindowsUEFICA2023Capable)" - Write-Host " HighConfidenceOptOut : $($reg.HighConfidenceOptOut)" - if ($reg.UEFICA2023Error) { - Write-Host " ! UEFICA2023Error : $($reg.UEFICA2023Error)" -ForegroundColor Red - } - Write-Host '' - - Write-Host 'EVENT LOG (relevantní EventID: 1795/1796/1800-1803/1808)' -ForegroundColor Yellow - Write-Host " Poslední event : EventID $($evt.LastEventId) ($($evt.LastEventTime))" - Write-Host " Confidence level : $($evt.ConfidenceLevel)" - if ($evt.RelevantEvents.Count -gt 0) { - Write-Host " Posledních $([Math]::Min($evt.RelevantEvents.Count,5)) záznamů:" - foreach ($e in ($evt.RelevantEvents | Select-Object -First 5)) { - $ec = if ($e.EventId -eq 1808) { 'Green' } - elseif ($e.EventId -in @(1795,1801)) { 'Red' } - else { 'DarkGray' } - Write-Host " [$($e.TimeCreated)] EventID $($e.EventId) $($e.Level)" -ForegroundColor $ec - } - } else { - Write-Host ' Žádné relevantní záznamy nenalezeny.' -ForegroundColor DarkGray - } - Write-Host '' - - Write-Host 'VÝSLEDEK' -ForegroundColor Yellow - Write-Host " $($result.CategoryLabel)" -ForegroundColor $catColor - Write-Host " Doporučená akce: $($result.RemediationAction)" -ForegroundColor White - Write-Host '' - Write-Host $line -ForegroundColor Cyan - Write-Host '' -} - -if ($JsonOnly) { - Write-Output $jsonOutput -} - -if ($PassThru) { - return $result -} - -#endregion \ No newline at end of file diff --git a/Invoke-SecureBootRemediation.ps1 b/Invoke-SecureBootRemediation.ps1 index 8054129..7cb0b74 100644 --- a/Invoke-SecureBootRemediation.ps1 +++ b/Invoke-SecureBootRemediation.ps1 @@ -1380,6 +1380,20 @@ $prereqs = Get-Prerequisites -R $result -IsAdmin $isAdmin # Zobrazení stavu (4 sekce: předpoklady, certifikáty, registry, události) Show-Status -R $result -Prereqs $prereqs +# ── VMware: upozornění na manuální import KEK certifikátu ──────────────────── +# Pokud bylo VM vytvořeno na ESXi starším než 9.x, nelze KEK nasadit automaticky. +if ($result.EnvironmentType -eq 'VMware VM' -and $result.SecureBoot.IsEnabled) { + Write-Host '' + Write-Host ' ┌─ UPOZORNĚNÍ — VMware VM ───────────────────────────────────────────────────┐' -ForegroundColor Yellow + Write-Host ' │ Pokud bylo toto VM vytvořeno na VMware ESXi starším než verze 9.x, │' -ForegroundColor Yellow + Write-Host ' │ certifikát KEK nelze nasadit automaticky a vyžaduje ruční import do │' -ForegroundColor Yellow + Write-Host ' │ UEFI firmware VM prostřednictvím správce ESXi hostitele. │' -ForegroundColor Yellow + Write-Host ' │ │' -ForegroundColor Yellow + Write-Host ' │ Postup: https://totalservice.atlassian.net/browse/KB-543 │' -ForegroundColor Cyan + Write-Host ' └──────────────────────────────────────────────────────────────────────────┘' -ForegroundColor Yellow + Add-LogLine 'Upozornění: VMware VM — na ESXi < 9.x je nutný ruční import KEK (KB-543)' +} + # ── CheckOnly: konec bez akce ───────────────────────────────────────────────── if ($CheckOnly) { Write-Host ''; Write-Rule @@ -1504,6 +1518,30 @@ if ($isWhatIf) { $autoRestartNote = if ($AutoRestart) { 'ANO (-AutoRestart)' } else { 'NE (nabídne dotaz)' } Write-Host (" - Restart serveru: {0}" -f $autoRestartNote) -ForegroundColor Gray } else { + # ── Upozornění na BitLocker před souhlasem s remediací ─────────────────── + # Zobrazuje se vždy, když je BitLocker aktivní — bez ohledu na typ protektoru. + $blPre = $result.BitLocker + $blPreActv = $blPre -and ($blPre.Status -eq 'On' -or $blPre.Status -eq '1' -or $blPre.Status -eq '2') + if ($blPreActv) { + Write-Host '' + if ($blPre.UsesPcr) { + Write-Host ' ┌─ DOPORUČENÍ PŘED REMEDIACÍ — BitLocker ───────────────────────────────────┐' -ForegroundColor Yellow + Write-Host ' │ Systémový svazek je chráněn nástrojem BitLocker s vazbou na TPM/PCR7. │' -ForegroundColor Yellow + Write-Host ' │ Aktualizace Boot Manageru může při příštím restartu vyvolat recovery │' -ForegroundColor Yellow + Write-Host ' │ prompt a znemožnit automatické spuštění serveru. │' -ForegroundColor Yellow + Write-Host ' │ │' -ForegroundColor Yellow + Write-Host ' │ Před zahájením remediace ověřte dostupnost recovery klíče: │' -ForegroundColor Yellow + Write-Host ' │ Active Directory (AD DS), Azure AD nebo bezpečný trezor klíčů. │' -ForegroundColor Yellow + Write-Host ' └──────────────────────────────────────────────────────────────────────────┘' -ForegroundColor Yellow + } else { + Write-Host ' ┌─ DOPORUČENÍ PŘED REMEDIACÍ — BitLocker ───────────────────────────────────┐' -ForegroundColor Yellow + Write-Host ' │ Systémový svazek je chráněn nástrojem BitLocker. │' -ForegroundColor Yellow + Write-Host ' │ Před zahájením remediace ověřte dostupnost recovery klíče (AD, trezor). │' -ForegroundColor Yellow + Write-Host ' └──────────────────────────────────────────────────────────────────────────┘' -ForegroundColor Yellow + } + Add-LogLine ("Upozornění: BitLocker aktivní před remediací (Protectors: {0}, PCR: {1})" -f $blPre.Protectors, $blPre.UsesPcr) + } + # ── Nabídnout remediaci ────────────────────────────────────────────────── $question = switch ($result.Category) { 'UPDATE_NEEDED' { 'Server potřebuje aktualizaci Secure Boot certifikátů. Chcete zahájit proces?' } diff --git a/README.md b/README.md index 4441d7e..322e96c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,15 @@ Microsoft v upozornění [KB5062710](https://support.microsoft.com/en-us/topic/w **Dopad, pokud se nic neudělá:** server bude dál bootovat, ale přestane přijímat nové ochrany boot procesu — aktualizace Boot Manageru, revokace (DBX) a mitigace nově objevených boot-level zranitelností. Postupně se sníží ochrana a ovlivní to scénáře závislé na Secure Boot důvěře (BitLocker hardening, third-party bootloadery). -**Pozor — co je skutečné „hotovo":** nestačí jen mít nové certifikáty v KEK/DB. Aktualizace je dokončená, až když se systém **reálně bootuje z Boot Manageru podepsaného Windows UEFI CA 2023** (signalizuje `WindowsUEFICA2023Capable = 2` / Event 1808). Tento poslední krok se aplikuje **až po restartu**. Skript proto kontroluje i Boot Manager, ne jen certifikáty. +**Co je skutečné „hotovo":** nestačí jen mít nové certifikáty v KEK/DB — aktualizace je dokončená až když jsou splněny **všechny 4 podmínky** a Boot Manager je ověřen: + +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 +5. Boot Manager: `WindowsUEFICA2023Capable = 2` nebo `certutil` ověřil podpis souboru `bootmgfw.efi` + +Poslední krok (aktivace Boot Manageru) se aplikuje **až po restartu** a skript to ověří přímo na souboru. --- @@ -26,11 +34,11 @@ Microsoft v upozornění [KB5062710](https://support.microsoft.com/en-us/topic/w - **Windows PowerShell 5.1+**, spuštěno jako **Administrator** - Build Windows z **14. 10. 2025 nebo novější** ([KB5066835](https://support.microsoft.com/en-us/topic/october-14-2025-kb5066835-os-builds-26200-6899-and-26100-6899-1db237d8-9f3b-4218-9515-3e0a32729685)) — bez něj neexistují servicing registry klíče ani úloha. Skript na to upozorní. -- Žádné externí moduly. UEFI databáze čte nativně. +- Žádné externí moduly. UEFI databáze a podpis bootloaderu čte nativně. ### Skript NIKDY nerestartuje server -Restart je nutný k dokončení aktualizace, ale necháváme ho **na vás** (plánované okno, ohled na BitLocker). Skript jen jasně řekne, kdy je restart potřeba. +Restart je nutný k dokončení aktualizace, ale necháváme ho **na vás** (plánované okno, ohled na BitLocker). Výjimka: parametr `-AutoRestart` pro automatizované scénáře. ### Typický průběh (interaktivně) @@ -39,15 +47,15 @@ Restart je nutný k dokončení aktualizace, ale necháváme ho **na vás** (pl .\Invoke-SecureBootRemediation.ps1 ``` -Skript provede detekci, ukáže **checklist** a u stavů, kde to dává smysl, se **zeptá**, zda remediaci aplikovat. Po souhlasu nastaví registry a spustí servicing úlohu (počká na ni). Pak: +Skript provede detekci, zobrazí výstup ve 4 sekcích a u stavů, kde to dává smysl, se **zeptá**, zda remediaci aplikovat. Po souhlasu nastaví registry a spustí servicing úlohu (počká na ni). Pak: ```text 2) Naplánujte RESTART serveru 3) Po restartu spusťte skript znovu -4) Opakujte, dokud checklist nebude celý zelený (HOTOVO) +4) Opakujte, dokud stav nebude HOTOVO ``` -Aktualizace se aplikuje **po částech na více restartů** — hodnota `AvailableUpdates` postupně klesá `0x5944 → 0x5904 → 0x5104 → 0x4104 → 0x4100 → 0x4000 → 0x0`. Cílový stav je `AvailableUpdates=0x0`, `UEFICA2023Status=Updated`, `WindowsUEFICA2023Capable=2`. +Aktualizace se aplikuje **po částech na více restartů** — hodnota `AvailableUpdates` postupně klesá `0x5944 → 0x5904 → 0x5104 → 0x4104 → 0x4100 → 0x4000 → 0x0`. ### Důležité před restartem @@ -57,63 +65,95 @@ Pokud má server **BitLocker s ochranou vázanou na TPM/PCR7**, změna Boot Mana | Parametr | Účel | |---|---| -| *(bez parametru)* | Interaktivní detekce → dotaz → remediace (bez restartu) | -| `-CheckOnly` | Jen detekce + checklist + **exit kód** (0 = hotovo, 1 = nutná akce, 2 = blokováno). Bez změn a bez dotazu. Vhodné pro RMM/monitoring. | -| `-WhatIf` | Ukáže, co by remediace udělala, bez provedení změn | -| `-AssumeYes` | Neinteraktivně aplikuje remediaci (pokud je smysluplná). Stále bez restartu. | -| `-Force` | Aplikuje i ve stavech, kdy to skript jinak nedoporučuje | -| `-SkipScheduledTask` | Nastaví registry, ale nespustí úlohu (spustí se sama, cca á 12 h) | -| `-SkipBootManagerFileCheck` | Vynechá mount ESP + `certutil`. Dokončení se i tak pozná z `WindowsUEFICA2023Capable=2` / Event 1808 / 1799. | -| `-RegisterResume` | Po remediaci vyžadující restart nastaví `RunOnce`, který po PŘÍŠTÍM (ručním) restartu sám spustí `-CheckOnly`. Nikdy nerestartuje. | -| `-Detailed` | Rozšířený rozpis (certifikáty, události, Boot Manager chain, bit-rozklad `AvailableUpdates`) | -| `-PassThru` | Vrátí výsledný objekt do pipeline | -| `-LogPath ` | Vlastní cesta k logu (jinak log vzniká vedle skriptu jen při reálné remediaci) | +| *(bez parametru)* | Interaktivní detekce → dotaz → remediace | +| `-CheckOnly` | Jen detekce + výpis + **exit kód** (0 = hotovo, 1 = nutná akce, 2 = blokováno). Bez změn a bez dotazu. Vhodné pro RMM/monitoring. | +| `-WhatIf` | Ukáže, co by remediace udělala, bez provedení změn. | +| `-AssumeYes` | Přeskočí interaktivní dotaz a remediaci rovnou aplikuje (pokud je smysluplná). | +| `-Force` | Spustí remediaci i v případě, že povinné certifikáty jsou již přítomny. | +| `-AutoRestart` | Po stavu `AU=0x4100` (Boot Manager staged, čeká na restart) restartuje server automaticky bez dotazu. **POZOR: okamžitý restart.** | +| `-EspDriveLetter ` | Písmeno jednotky pro dočasný mount ESP při ověřování Boot Manageru (výchozí `S`). Změňte, pokud `S:` je na serveru obsazeno. | ### Příklady ```powershell -# Jen kontrola stavu (pro skript/monitoring), bez zásahu +# Jen kontrola stavu — pro skript/monitoring, bez zásahu .\Invoke-SecureBootRemediation.ps1 -CheckOnly # Náhled co by se stalo, bez změn .\Invoke-SecureBootRemediation.ps1 -WhatIf -# Neinteraktivní aplikace + auto-kontrola po příštím restartu -.\Invoke-SecureBootRemediation.ps1 -AssumeYes -RegisterResume +# Neinteraktivní aplikace (CI/CD, dávkový rollout) +.\Invoke-SecureBootRemediation.ps1 -AssumeYes -# Plný rozpis pro diagnostiku -.\Invoke-SecureBootRemediation.ps1 -CheckOnly -Detailed +# Server kde S: je obsazeno, použít T: pro ESP +.\Invoke-SecureBootRemediation.ps1 -EspDriveLetter T + +# Neinteraktivní + auto-restart po staged Boot Manageru +.\Invoke-SecureBootRemediation.ps1 -AssumeYes -AutoRestart ``` --- ## 3. Co skript dělá (detailně) -### Fázový checklist +### Výstup — 4 sekce -Server je „HOTOVO" teprve když projdou všechny **povinné** fáze. Volitelné fáze jsou jen informativní. +Skript zobrazí vždy čtyři sekce: + +``` +PŘEDPOKLADY: + 1. [+] Administrátorská práva + 2. [+] Windows Server — Zjištěno: Windows Server 2022 Standard (build 20348.xxx) + 3. [+] UEFI + Secure Boot zapnutý + 4. [+] Úroveň záplat >= 10/2025 — stavové registry klíče přítomny + 5. [+] Scheduled task 'Secure-Boot-Update' k dispozici + 6. [+] PowerShell SecureBoot cmdlety — Get-SecureBootUEFI dostupný + +CERTIFIKÁTY: + [+] Windows UEFI CA 2023 v DB (povinný) platný do 2045-06-10 + [+] Microsoft Corporation KEK 2K CA 2023 (povinný) + [ ] Boot Manager (bootmgfw.efi) nedostupný (ESP nelze připojit) + [ ] Microsoft UEFI CA 2023 (volitelný) + [ ] Option ROM UEFI CA 2023 (volitelný) + +REGISTRY: + AvailableUpdates : 0x5944 (naplánováno — start) + UEFICA2023Status : NotStarted + UEFICA2023Error : (žádná) + WindowsUEFICA2023Capable : 1 = cert v DB (boot mgr zatím ne) + +UDÁLOSTI (System log — Secure Boot): + 2026-06-05 18:42 EventID 1801 Čeká na podmínky (monitor) + 2026-06-05 18:43 EventID 1808 Certifikáty úspěšně aplikovány +``` + +Pokud hard prerekvizita selže, skript dále nepokračuje a zobrazí přesný důvod. + +### Fáze — co je povinné pro HOTOVO | Fáze | Povinná | Splněno když | |---|---|---| | Secure Boot zapnutý | ✅ | `Confirm-SecureBootUEFI` = True | -| Servicing task k dispozici | ✅ | existuje `\Microsoft\Windows\PI\Secure-Boot-Update` | +| Servicing task k dispozici | ✅ | task `Secure-Boot-Update` existuje | | KEK: Microsoft Corporation KEK 2K CA 2023 | ✅ | cert v KEK databázi | | DB: Windows UEFI CA 2023 | ✅ | cert v DB databázi | -| Boot Manager aktivní (2023) | ✅ | `WindowsUEFICA2023Capable=2` / Event 1808 / 1799 | -| DB: Microsoft UEFI CA 2023 (3rd-party) | — | volitelné (pro option ROM / non-Windows boot) | +| AvailableUpdates = 0x0 nebo 0x4000 | ✅ | servisování dokončeno | +| UEFICA2023Status = Updated | ✅ | registry klíč | +| Boot Manager ověřen (2023 CA) | ✅ | Capable=2 nebo certutil potvrdil podpis souboru | +| DB: Microsoft UEFI CA 2023 (3rd-party) | — | volitelné (option ROM / non-Windows boot) | | DB: Option ROM UEFI CA 2023 | — | volitelné | | DBX: revokace starého boot manageru 2011 | — | volitelné (finální hardening) | -V checklistu: `[✓]` splněno (zeleně), `[ ]` zbývá, `[✗]` chyba/blokováno. Zvýrazní se aktuální krok („← vyžaduje RESTART"). - ### Detekce — odkud čte - **Prostředí a HW** (WMI/CIM): fyzický / Hyper-V VM / VMware VM, výrobce, model, BIOS verze a datum - **Secure Boot stav** a **operating mode** (User / Deployed / **Setup** / Audit) ze standardních UEFI proměnných -- **Certifikáty** v KEK / DB / DBX — primárně parsováním `EFI_SIGNATURE_LIST` na X.509 (subject, thumbprint, platnost), s ASCII fallbackem -- **Boot Manager** `bootmgfw.efi` na ESP — read-only mount + `certutil -dump` (staged podpis); aktivní stav z registru/eventů +- **Certifikáty** v KEK / DB / DBX — parsováním `EFI_SIGNATURE_LIST` na X.509 (subject, thumbprint, platnost), s ASCII fallbackem +- **Boot Manager** `bootmgfw.efi` na ESP: + - Pokus 1: prohledá A–Z pro ESP s existujícím písmenem jednotky + - Pokus 2: připojí ESP přes `mountvol : /S` (výchozí S:, lze změnit `-EspDriveLetter`), zkopíruje soubor do `%TEMP%\SecureBootCA2023\` a ověří podpis přes `certutil -dump` — přečte embedded PKCS#7 blob přímo z PE souboru, bez závislosti na local cert store; záloha Event 1799 - **Registry** `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot[\Servicing]`: - `AvailableUpdates`, `MicrosoftUpdateManagedOptIn`, `HighConfidenceOptOut`, `UEFICA2023Status` (REG_SZ: NotStarted/InProgress/**Updated**), `UEFICA2023Error`, `UEFICA2023ErrorEvent`, `WindowsUEFICA2023Capable` (0/1/**2**), `ConfidenceLevel` + `AvailableUpdates`, `MicrosoftUpdateManagedOptIn`, `HighConfidenceOptOut`, `UEFICA2023Status` (NotStarted / InProgress / **Updated** / Failed), `UEFICA2023Error`, `UEFICA2023ErrorEvent`, `WindowsUEFICA2023Capable` (0 / 1 / **2**), `ConfidenceLevel` - **BitLocker** systémové jednotky: stav ochrany a typ protektoru (zvýraznění TPM/PCR7 rizika) - **Event Log** (System, zdroj TPM-WMI): 1795, 1796, 1799, 1800, 1801, 1802, 1803, 1808 @@ -121,41 +161,45 @@ V checklistu: `[✓]` splněno (zeleně), `[ ]` zbývá, `[✗]` chyba/blokován | Kategorie | Význam | Akce | |---|---|---| -| **OK** | kompletní 2023 sada, aktivní Boot Manager, 2011 odstraněny | žádná | -| **OK_TRANSITION** | 2023 i aktivní Boot Manager hotové, 2011 ještě přítomné (normální) | žádná, monitorovat | -| **UPDATE_NEEDED** | nic 2023 nenasazeno | zahájit remediaci | -| **UPDATE_PARTIAL** | část nasazena, chybí KEK/DB | pokračovat v cyklu | -| **UPDATE_PENDING** | KEK i DB hotové, zbývá aktivovat Boot Manager | **RESTART**, pak znovu zkontrolovat | +| **OK** | 4 podmínky splněny + Boot Manager ověřen | žádná | +| **OK_TRANSITION** | HOTOVO, ale staré 2011 certy ještě přítomné (normální přechodný stav) | žádná, monitorovat | +| **UPDATE_NEEDED** | Žádné 2023 certy nenasazeny | zahájit remediaci | +| **UPDATE_PARTIAL** | Část certifikátů nasazena, servisování probíhá | spustit task, restart | +| **UPDATE_PENDING_RESTART** | Boot Manager 2023 staged (`AU=0x4100`) — čeká na restart | **RESTART** | +| **UPDATE_BOOTMANAGER** | Certy v DB/KEK OK, ale bootloader dosud nepoužívá 2023 CA | spustit task, restart | | **UPDATE_FAILED** | Event 1795 / `UEFICA2023ErrorEvent` / status Failed | firmware/OEM, troubleshooting | -| **FIRMWARE_UPDATE_NEEDED** | ConfidenceLevel „Temporarily Paused" / Event 1802 | update firmwaru u OEM | -| **NOT_SUPPORTED** | ConfidenceLevel „Not Supported" | dokumentovat jako výjimku | +| **FIRMWARE_UPDATE_NEEDED** | `ConfidenceLevel: Temporarily Paused` / Event 1802 | update firmwaru u OEM | +| **NOT_SUPPORTED** | `ConfidenceLevel: Not Supported` | dokumentovat jako výjimku | +| **BUILD_OUTDATED** | Certy přítomny, ale chybí servicing infrastruktura | Windows Update (KB5066835+) | +| **TASK_MISSING** | Chybí servicing úloha | nainstalovat KB5066835+ | | **SETUP_MODE** | Secure Boot v Setup Mode (chybí enrolled PK) | obnovit klíče v UEFI | -| **TASK_MISSING** | chybí servicing úloha (starý build) | nainstalovat KB5066835+ | -| **SECUREBOOT_DISABLED** | Secure Boot vypnutý | rozhodnout o zapnutí | -| **NO_SECUREBOOT / _VM** | Legacy BIOS / VM bez vTPM | výjimka | +| **SECUREBOOT_DISABLED** | Secure Boot vypnutý | zapnout v UEFI/BIOS | +| **NO_SECUREBOOT** | Legacy BIOS | dokumentovat jako výjimku | +| **NO_SECUREBOOT_VM** | VM bez UEFI nebo vTPM | dokumentovat jako výjimku | ### Remediace (jen po souhlasu, sekvenčně) -1. **Registry** — nastaví `MicrosoftUpdateManagedOptIn=1`, `AvailableUpdates=0x5944`, `HighConfidenceOptOut=0`; zápis se ověří zpětným čtením, než pokračuje dál. -2. **Servicing úloha** — spustí `\Microsoft\Windows\PI\Secure-Boot-Update` a **čeká na změnu** `AvailableUpdates` (nebo na doběh úlohy, timeout 180 s). -3. **Ověření** — znovu proběhne detekce a ukáže aktualizovaný stav fází. +1. **Registry** — nastaví `MicrosoftUpdateManagedOptIn=1`, `AvailableUpdates=0x5944` (jen při první inicializaci nebo po dokončení), `HighConfidenceOptOut=0`; zápis se ověří zpětným čtením. +2. **Servicing úloha** — spustí `\Microsoft\Windows\PI\Secure-Boot-Update` a čeká na změnu `AvailableUpdates` (timeout 180 s). +3. **Ověření** — znovu proběhne detekce a zobrazí aktualizovaný stav. -Skript pak vypíše další kroky (restart, BitLocker, opakování) — **bez restartu**. +Po remediaci skript jasně řekne, kdy je restart nutný a upozorní na BitLocker — **bez restartu**. -### Stav napříč restarty +### Stav v log souboru -Průběh se ukládá do `%ProgramData%\SecureBootCA2023\state.json` (číslo cyklu, kategorie, hodnota). S `-RegisterResume` se přes `RunOnce` po dalším restartu sama spustí `-CheckOnly` (žádné auto-aplikování, žádný auto-restart). +Průběh se loguje do `%ProgramData%\SecureBootCA2023\SecureBootRemediation.log` a stav cyklu do `%ProgramData%\SecureBootCA2023\state.json`. -### Výstup +### Výstup pro automatizaci (`-CheckOnly` exit kódy) -- Stručný **barevný** souhrn: server, checklist, registry/firmware, verdikt -- Český, faktický, nezahlcuje; důležité informace jsou zvýrazněné -- Volitelný **log** (cesta se vypíše), `-Detailed` rozpis, `-PassThru` objekt -- `-CheckOnly` nastavuje **exit kód** (0/1/2) pro automatizaci +| Exit kód | Význam | +|---|---| +| `0` | HOTOVO — OK nebo OK_TRANSITION | +| `1` | Nutná akce — UPDATE_* kategorie | +| `2` | Blokováno — prerekvizita selhala, NO_SECUREBOOT, SETUP_MODE, apod. | ### Kódování -Skript je uložen jako **UTF-8 s BOM** a na startu vynutí UTF-8 výstup konzole, aby čeština i symboly (`✓`) fungovaly i ve Windows PowerShell 5.1. Symboly checklistu jsou na jednom místě nahoře ve skriptu (`$SYM_DONE` …), kdyby je bylo potřeba změnit. +Skript je uložen jako **UTF-8 s BOM** a na startu vynutí UTF-8 výstup konzole, aby čeština i symboly fungovaly i ve Windows PowerShell 5.1. --- @@ -166,13 +210,11 @@ Skript je uložen jako **UTF-8 s BOM** a na startu vynutí UTF-8 výstup konzole | Téma | Odkaz | |---|---| | Přehled: expirace certifikátů a CA aktualizace (KB5062710) | [support.microsoft.com](https://support.microsoft.com/en-us/topic/windows-secure-boot-certificate-expiration-and-ca-updates-7ff40d33-95dc-4c3c-8725-a9b95457578e) | -| Guidance for IT Professionals and Organizations | [support.microsoft.com](https://support.microsoft.com/en-us/topic/secure-boot-certificate-updates-guidance-for-it-professionals-and-organizations-e2b43f9f-b424-42df-bc6a-8476db65ab2f) | -| Registry key updates for Secure Boot (IT-managed) | [support.microsoft.com](https://support.microsoft.com/en-us/topic/registry-key-updates-for-secure-boot-windows-devices-with-it-managed-updates-a7be69c9-4634-42e1-9ca1-df06f43f360d) | -| Secure Boot DB and DBX variable update events | [support.microsoft.com](https://support.microsoft.com/en-us/topic/secure-boot-db-and-dbx-variable-update-events-37e47cf8-608b-4a87-8175-bdead630eb69) | -| IT Pro guidance (KB5062713) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5062713) | -| Registry key metoda (KB5068202) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5068202) | +| Guidance for IT Professionals and Organizations (KB5062713) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5062713) | +| Registry key updates for Secure Boot — IT-managed (KB5068202) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5068202) | | GPO metoda (KB5068198) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5068198) | | WinCS API (KB5068197) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5068197) | +| Secure Boot DB and DBX variable update events | [support.microsoft.com](https://support.microsoft.com/en-us/topic/secure-boot-db-and-dbx-variable-update-events-37e47cf8-608b-4a87-8175-bdead630eb69) | | Troubleshooting (KB5085046) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5085046) | | Known issues a resolutions, vč. Hyper-V fix (KB5085790) | [support.microsoft.com](https://support.microsoft.com/en-us/help/5085790) | | Minimální build 14. 10. 2025 (KB5066835) | [support.microsoft.com](https://support.microsoft.com/en-us/topic/october-14-2025-kb5066835-os-builds-26200-6899-and-26100-6899-1db237d8-9f3b-4218-9515-3e0a32729685) | @@ -183,8 +225,8 @@ Skript je uložen jako **UTF-8 s BOM** a na startu vynutí UTF-8 výstup konzole | Projekt | Odkaz | |---|---| | CheckCA2023 (claude-boucher) — GUI monitor, podrobná registry/bit reference | [github.com/claude-boucher/CheckCA2023](https://github.com/claude-boucher/CheckCA2023) | -| SecureBoot-CA2023-Automatic-Update (mathisokle) — automatizace přes restarty | [github.com/mathisokle/SecureBoot-CA2023-Automatic-Update](https://github.com/mathisokle/SecureBoot-CA2023-Automatic-Update) | +| SecureBoot-CA2023-Automatic-Update (mathisokle) — automatizace, ESP mount logika | [github.com/mathisokle/SecureBoot-CA2023-Automatic-Update](https://github.com/mathisokle/SecureBoot-CA2023-Automatic-Update) | --- -*Skript záměrně NErestartuje server. Implementuje metodu registry klíčů (KB5068202); metody GPO / Intune / WinCS nepokrývá.* +*Skript záměrně NErestartuje server (výjimka: `-AutoRestart`). Implementuje metodu registry klíčů (KB5068202); metody GPO / Intune / WinCS nepokrývá.* diff --git a/Set-SecureBootCertificateUpdate.ps1 b/Set-SecureBootCertificateUpdate.ps1 deleted file mode 100644 index 44cbbe2..0000000 --- a/Set-SecureBootCertificateUpdate.ps1 +++ /dev/null @@ -1,519 +0,0 @@ -#Requires -Version 5.1 -<# -.SYNOPSIS - Remediační skript pro aktualizaci Secure Boot certifikátů (KB5068202). - -.DESCRIPTION - Nastaví registry klíče pro zahájení aktualizace Secure Boot certifikátů - (přechod z 2011 na 2023 sadu) a spustí scheduled task. - - Metoda: Registry key (KB5068202) - Alternativy: GPO (KB5068198), WinCS CLI (KB5068197) - - Postup remediace: - 1. Skript nastaví AvailableUpdates = 0x5944 v registry - 2. Scheduled task \Microsoft\Windows\PI\Secure-Boot-Update se spustí - (nebo čeká na příští plánovaný běh každých 12 hodin) - 3. Certifikáty se aplikují při příštím restartu - 4. Výsledek ověřit přes EventID 1808 (úspěch) nebo 1801/1795 (chyba) - - DŮLEŽITÉ: - - Aktualizace vyžaduje RESTART pro aplikaci certifikátů v Boot Manageru - - Neúspěšná aktualizace NEOHROZÍ boot serveru — server běží dál - - Po aplikaci čekat min. 48 hodin a jeden nebo více restartů - - Na Hyper-V VM s Event 1795: ověřit verzi hostitele (fix dostupný od 3/2026) - -.PARAMETER ServerName - Název nebo pole názvů serverů pro vzdálené spuštění přes WinRM. - Pokud není zadán, skript se spustí lokálně. - -.PARAMETER Credential - Přihlašovací údaje pro vzdálené připojení. - -.PARAMETER Force - Aplikuje registry klíče i na serverech, které již mají 2023 certifikáty - nebo kde již aktualizace proběhla (UEFICA2023Status = 2). - -.PARAMETER SkipScheduledTask - Nespustí scheduled task po nastavení registry. Task se spustí sám - při příštím plánovaném běhu (každých 12 hodin). - -.PARAMETER VerifyOnly - Pouze zobrazí aktuální stav bez provedení změn (read-only, ekvivalent -WhatIf). - -.PARAMETER LogPath - Cesta k logovacímu souboru. Default: .\SecureBootRemediation-.log - -.EXAMPLE - # WhatIf — zobrazí co by se stalo, bez změn - .\Set-SecureBootCertificateUpdate.ps1 -WhatIf - -.EXAMPLE - # Lokální spuštění - .\Set-SecureBootCertificateUpdate.ps1 - -.EXAMPLE - # Vzdálené spuštění na jednom serveru - .\Set-SecureBootCertificateUpdate.ps1 -ServerName SERVER01 - -.EXAMPLE - # Vzdálené spuštění na více serverech - .\Set-SecureBootCertificateUpdate.ps1 -ServerName SERVER01,SERVER02,SERVER03 - -.EXAMPLE - # Pouze ověření bez změn - .\Set-SecureBootCertificateUpdate.ps1 -ServerName SERVER01 -VerifyOnly - -.NOTES - Reference: KB5062710, KB5068202, KB5085046, KB5085790 - Vyžaduje spuštění jako Administrator (lokálně i vzdáleně). - Vzdálené spuštění vyžaduje WinRM (standardně aktivní na Windows Server). - AvailableUpdates = 0x5944 dle KB5068202 (KEK + UEFI CA + Windows UEFI CA + Boot Manager). -#> - -[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] -param( - [Parameter(Position = 0)] - [string[]]$ServerName, - - [System.Management.Automation.PSCredential]$Credential, - - [switch]$Force, - [switch]$SkipScheduledTask, - [switch]$VerifyOnly, - - [string]$LogPath = ".\SecureBootRemediation-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" -) - -$ErrorActionPreference = 'SilentlyContinue' -Set-StrictMode -Off - -#region ── Logging ──────────────────────────────────────────────────────────── - -function Write-Log { - param( - [string]$Message, - [ValidateSet('INFO','WARN','ERROR','SUCCESS','ACTION')] - [string]$Level = 'INFO' - ) - $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' - $line = "[$ts] [$Level] $Message" - - $color = switch ($Level) { - 'SUCCESS' { 'Green' } - 'WARN' { 'Yellow' } - 'ERROR' { 'Red' } - 'ACTION' { 'Cyan' } - default { 'Gray' } - } - Write-Host $line -ForegroundColor $color - - try { - $line | Out-File -FilePath $LogPath -Append -Encoding UTF8 -Force - } catch { } -} - -#endregion - -#region ── Core remediation logic (spouští se lokálně nebo přes Invoke-Command) ── - -$RemediationScriptBlock = { - param( - [bool]$WhatIfMode, - [bool]$ForceMode, - [bool]$VerifyOnlyMode, - [bool]$SkipTask - ) - - $result = [ordered]@{ - Hostname = $env:COMPUTERNAME - Timestamp = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ssZ') - PreState = $null - ActionsApplied = @() - PostState = $null - Status = 'Unknown' - Message = '' - RequiresRestart = $false - } - - # Registry konstanty (KB5068202) - $REG_SECUREBOOT = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot' - $REG_SERVICING = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing' - $AVAILABLE_UPDATES_VALUE = 0x5944 # KEK + UEFI CA + Windows UEFI CA + Boot Manager (KB5068202) - $TASK_PATH = '\Microsoft\Windows\PI\Secure-Boot-Update' - - #-- Funkce: zjistit aktuální stav ----------------------------------------- - function Get-State { - $s = [ordered]@{ - FirmwareType = 'Unknown' - SecureBootEnabled = $false - SecureBootSupported = $false - AvailableUpdates = $null - HighConfidenceOptOut = $null - UEFICA2023Status = $null - UEFICA2023StatusText = 'Unknown' - UEFICA2023Error = $null - WindowsUEFICA2023Capable = $null - LastRelevantEvent = $null - LastRelevantEventTime = $null - } - - # Firmware type - $fw = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control' -Name PEFirmwareType -ErrorAction SilentlyContinue - if ($fw) { - $s.FirmwareType = if ($fw.PEFirmwareType -eq 2) { 'UEFI' } else { 'Legacy BIOS' } - } elseif (Test-Path $REG_SECUREBOOT) { - $s.FirmwareType = 'UEFI' - } else { - $s.FirmwareType = 'Legacy BIOS' - } - - # Secure Boot stav - try { - $r = Confirm-SecureBootUEFI -ErrorAction Stop - $s.SecureBootSupported = $true - $s.SecureBootEnabled = [bool]$r - } catch { } - - # Registry hodnoty - $mainProps = Get-ItemProperty $REG_SECUREBOOT -ErrorAction SilentlyContinue - if ($mainProps) { - $s.AvailableUpdates = $mainProps.AvailableUpdates - $s.HighConfidenceOptOut = $mainProps.HighConfidenceOptOut - } - - $svcProps = Get-ItemProperty $REG_SERVICING -ErrorAction SilentlyContinue - if ($svcProps) { - $s.UEFICA2023Status = $svcProps.UEFICA2023Status - $s.UEFICA2023Error = $svcProps.UEFICA2023Error - $s.WindowsUEFICA2023Capable = $svcProps.WindowsUEFICA2023Capable - - $s.UEFICA2023StatusText = switch ($s.UEFICA2023Status) { - 0 { 'NotStarted' } - 1 { 'InProgress' } - 2 { 'Success' } - 3 { 'Failed' } - $null { 'KeyNotPresent' } - default { "Unknown ($($s.UEFICA2023Status))" } - } - } - - # Poslední relevantní event - try { - $evt = Get-WinEvent -FilterHashtable @{ - LogName = 'System' - Id = @(1795,1801,1808) - } -MaxEvents 1 -ErrorAction Stop | Select-Object -First 1 - - if ($evt) { - $s.LastRelevantEvent = $evt.Id - $s.LastRelevantEventTime = $evt.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') - } - } catch { } - - return $s - } - - #-- Představ aktuální stav ------------------------------------------------ - $result.PreState = Get-State - - $pre = $result.PreState - - # Abort podmínky (bez -Force) - if (-not $VerifyOnlyMode -and -not $WhatIfMode) { - if ($pre.FirmwareType -ne 'UEFI') { - $result.Status = 'Skipped' - $result.Message = 'Legacy BIOS — Secure Boot není podporováno, remediace není možná.' - return $result - } - if (-not $pre.SecureBootSupported) { - $result.Status = 'Skipped' - $result.Message = 'Secure Boot není podporováno nebo povoleno — remediace přeskočena.' - return $result - } - if ($pre.UEFICA2023Status -eq 2 -and -not $ForceMode) { - $result.Status = 'Skipped' - $result.Message = "Aktualizace již byla úspěšně dokončena (UEFICA2023Status=2). Použijte -Force pro opakované spuštění." - return $result - } - if ($pre.WindowsUEFICA2023Capable -eq 0 -and -not $ForceMode) { - $result.Status = 'Warning' - $result.Message = "WindowsUEFICA2023Capable=0 — firmware nemusí podporovat nové certifikáty. Ověřte firmware u výrobce. Použijte -Force pro vynucení." - return $result - } - if ($pre.LastRelevantEvent -eq 1795) { - $result.Status = 'Warning' - $result.Message = "Detekován Event 1795 (Hyper-V known issue). Ověřte verzi hostitele — fix je dostupný od 3/2026 (KB5085790). Použijte -Force pro pokračování." - if (-not $ForceMode) { return $result } - } - } - - if ($VerifyOnlyMode) { - $result.Status = 'VerifyOnly' - $result.Message = 'VerifyOnly mode — žádné změny neprovedeny.' - return $result - } - - if ($WhatIfMode) { - $result.ActionsApplied += "WhatIf: Nastavil by HKLM:\...\SecureBoot\AvailableUpdates = 0x$($AVAILABLE_UPDATES_VALUE.ToString('X4'))" - $result.ActionsApplied += "WhatIf: Zajistil by HighConfidenceOptOut = 0" - if (-not $SkipTask) { - $result.ActionsApplied += "WhatIf: Spustil by scheduled task '$TASK_PATH'" - } - $result.Status = 'WhatIf' - $result.Message = 'WhatIf mode — žádné změny neprovedeny.' - return $result - } - - #-- Aplikovat registry klíče (KB5068202) ---------------------------------- - - # 1) Zajistit existenci SecureBoot klíče (obvykle existuje) - if (-not (Test-Path $REG_SECUREBOOT)) { - try { - New-Item -Path $REG_SECUREBOOT -Force -ErrorAction Stop | Out-Null - $result.ActionsApplied += "Vytvořen registry klíč: $REG_SECUREBOOT" - } catch { - $result.Status = 'Error' - $result.Message = "Nelze vytvořit registry klíč $REG_SECUREBOOT : $($_.Exception.Message)" - return $result - } - } - - # 2) Nastavit AvailableUpdates = 0x5944 - try { - Set-ItemProperty -Path $REG_SECUREBOOT -Name 'AvailableUpdates' ` - -Value $AVAILABLE_UPDATES_VALUE -Type DWord -Force -ErrorAction Stop - $result.ActionsApplied += "Nastaveno AvailableUpdates = 0x$($AVAILABLE_UPDATES_VALUE.ToString('X4')) (KB5068202)" - } catch { - $result.Status = 'Error' - $result.Message = "Chyba při nastavení AvailableUpdates: $($_.Exception.Message)" - return $result - } - - # 3) HighConfidenceOptOut musí být 0 (nebo neexistovat) — jinak se update neprovede - $optOut = (Get-ItemProperty $REG_SECUREBOOT -Name 'HighConfidenceOptOut' -ErrorAction SilentlyContinue) - if ($optOut -and $optOut.HighConfidenceOptOut -ne 0) { - try { - Set-ItemProperty -Path $REG_SECUREBOOT -Name 'HighConfidenceOptOut' ` - -Value 0 -Type DWord -Force -ErrorAction Stop - $result.ActionsApplied += "Resetován HighConfidenceOptOut = 0 (byl $($optOut.HighConfidenceOptOut))" - } catch { - $result.ActionsApplied += "WARN: Nepodařilo se resetovat HighConfidenceOptOut: $($_.Exception.Message)" - } - } - - # 4) Zajistit existenci Servicing podklíče - if (-not (Test-Path $REG_SERVICING)) { - try { - New-Item -Path $REG_SERVICING -Force -ErrorAction Stop | Out-Null - $result.ActionsApplied += "Vytvořen podklíč: $REG_SERVICING" - } catch { } - } - - #-- Spustit scheduled task ------------------------------------------------ - if (-not $SkipTask) { - try { - $task = Get-ScheduledTask -TaskPath '\Microsoft\Windows\PI\' ` - -TaskName 'Secure-Boot-Update' -ErrorAction Stop - - if ($task) { - Start-ScheduledTask -TaskPath '\Microsoft\Windows\PI\' ` - -TaskName 'Secure-Boot-Update' -ErrorAction Stop - $result.ActionsApplied += "Spuštěn scheduled task: $TASK_PATH" - - # Čekat na dokončení tasku (timeout 120 sekund) - $timeoutSec = 120 - $intervalSec = 3 - $elapsed = 0 - $finalState = 'Unknown' - - do { - Start-Sleep -Seconds $intervalSec - $elapsed += $intervalSec - $finalState = (Get-ScheduledTask -TaskPath '\Microsoft\Windows\PI\' ` - -TaskName 'Secure-Boot-Update' ` - -ErrorAction SilentlyContinue).State - } while ($finalState -eq 'Running' -and $elapsed -lt $timeoutSec) - - if ($elapsed -ge $timeoutSec -and $finalState -eq 'Running') { - $result.ActionsApplied += "WARN: Task stále běží po $timeoutSec s — pokračuji bez čekání. Ověřit stav ručně." - } else { - $result.ActionsApplied += "Task dokončen za ${elapsed}s — stav: $finalState" - } - } - } catch { - $result.ActionsApplied += "INFO: Scheduled task '$TASK_PATH' nebyl nalezen nebo se nepodařilo spustit: $($_.Exception.Message)" - $result.ActionsApplied += "INFO: Task se spustí automaticky při příštím plánovaném běhu (každých 12 hodin)." - } - } else { - $result.ActionsApplied += "INFO: Spuštění scheduled task přeskočeno (-SkipScheduledTask). Task se spustí sám (každých 12 hodin)." - } - - #-- Post-stav (okamžitě po aplikaci, před restartem) --------------------- - Start-Sleep -Seconds 2 - $result.PostState = Get-State - - $result.RequiresRestart = $true - $result.Status = 'Applied' - $result.Message = "Registry klíče nastaveny. Server vyžaduje RESTART pro aplikaci certifikátů. Po restartu ověřit EventID 1808 v System logu." - - return $result -} - -#endregion - -#region ── Výstup výsledku ──────────────────────────────────────────────────── - -function Write-ResultSummary { - param($res) - - $line = '-' * 60 - Write-Log $line - Write-Log "SERVER: $($res.Hostname) | $($res.Timestamp)" - $_statusLevel = switch ($res.Status) { - 'Applied' { 'SUCCESS' } - 'Error' { 'ERROR' } - 'Warning' { 'WARN' } - 'Skipped' { 'WARN' } - default { 'INFO' } - } - Write-Log "STATUS: $($res.Status) — $($res.Message)" -Level $_statusLevel - - $pre = $res.PreState - if ($pre) { - Write-Log " Pre-state:" - Write-Log " Firmware : $($pre.FirmwareType)" - Write-Log " Secure Boot : podporováno=$($pre.SecureBootSupported), povoleno=$($pre.SecureBootEnabled)" - Write-Log " AvailableUpdates : $($pre.AvailableUpdates)" - Write-Log " UEFICA2023Status : $($pre.UEFICA2023Status) ($($pre.UEFICA2023StatusText))" - Write-Log " WindowsCapable : $($pre.WindowsUEFICA2023Capable)" - if ($pre.UEFICA2023Error) { - Write-Log " ! UEFICA2023Error : $($pre.UEFICA2023Error)" -Level 'WARN' - } - if ($pre.LastRelevantEvent) { - Write-Log " Poslední event : EventID $($pre.LastRelevantEvent) ($($pre.LastRelevantEventTime))" - } - } - - if ($res.ActionsApplied -and $res.ActionsApplied.Count -gt 0) { - Write-Log " Provedené akce:" - foreach ($action in $res.ActionsApplied) { - $lvl = if ($action -like 'WhatIf:*' -or $action -like 'INFO:*') { 'INFO' } - elseif ($action -like 'WARN:*') { 'WARN' } - else { 'ACTION' } - Write-Log " $action" -Level $lvl - } - } - - $post = $res.PostState - if ($post -and $res.Status -eq 'Applied') { - Write-Log " Post-state (před restartem):" - Write-Log " AvailableUpdates : $($post.AvailableUpdates)" - Write-Log " UEFICA2023Status : $($post.UEFICA2023Status) ($($post.UEFICA2023StatusText))" - } - - if ($res.RequiresRestart) { - Write-Log " *** VYŽADOVÁN RESTART pro aplikaci certifikátů ***" -Level 'WARN' - Write-Log " Po restartu ověřit: Get-WinEvent -FilterHashtable @{LogName='System';Id=1808} -MaxEvents 5" - } -} - -#endregion - -#region ── Main ─────────────────────────────────────────────────────────────── - -$isWhatIf = $WhatIfPreference -eq [Management.Automation.ActionPreference]::Continue -$isVerify = $VerifyOnly.IsPresent -$isForce = $Force.IsPresent -$isSkipTask = $SkipScheduledTask.IsPresent - -Write-Log ('=' * 60) -Write-Log "Set-SecureBootCertificateUpdate — START" -Write-Log "Parametry: WhatIf=$isWhatIf, Force=$isForce, VerifyOnly=$isVerify, SkipTask=$isSkipTask" -Write-Log "Log: $LogPath" -Write-Log ('=' * 60) - -$results = @() - -if (-not $ServerName -or $ServerName.Count -eq 0) { - # ── Lokální spuštění ── - Write-Log "Spouštím lokálně na: $env:COMPUTERNAME" -Level 'INFO' - - if (-not $isWhatIf -and -not $isVerify) { - if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Nastavit Secure Boot registry klíče (AvailableUpdates=0x5944)")) { - Write-Log "Uživatel zamítl akci — konec." -Level 'WARN' - exit 0 - } - } - - $res = & $RemediationScriptBlock -WhatIfMode $isWhatIf -ForceMode $isForce ` - -VerifyOnlyMode $isVerify -SkipTask $isSkipTask - Write-ResultSummary $res - $results += $res - -} else { - # ── Vzdálené spuštění ── - Write-Log "Cílové servery ($($ServerName.Count)): $($ServerName -join ', ')" -Level 'INFO' - - $invokeParams = @{ - ComputerName = $ServerName - ScriptBlock = $RemediationScriptBlock - ArgumentList = @($isWhatIf, $isForce, $isVerify, $isSkipTask) - ErrorAction = 'SilentlyContinue' - ThrottleLimit = 10 - } - if ($Credential) { $invokeParams['Credential'] = $Credential } - - $remoteResults = Invoke-Command @invokeParams - - foreach ($res in $remoteResults) { - Write-ResultSummary $res - $results += $res - } - - # Servery, které neodpověděly - $respondedHosts = $remoteResults | ForEach-Object { $_.Hostname } - foreach ($srv in $ServerName) { - if ($srv -notin $respondedHosts) { - Write-Log " ! Server '$srv' neodpověděl (WinRM nedostupný nebo přihlášení selhalo)" -Level 'ERROR' - $results += [ordered]@{ - Hostname = $srv; Status = 'Unreachable'; Message = 'WinRM nedostupný' - PreState = $null; PostState = $null; ActionsApplied = @(); RequiresRestart = $false - } - } - } -} - -#-- Souhrn ------------------------------------------------------------------ -Write-Log ('=' * 60) -Write-Log "SOUHRN REMEDIACE" - -$grouped = $results | Group-Object Status -foreach ($g in $grouped) { - $lvl = switch ($g.Name) { - 'Applied' { 'SUCCESS' } - 'Error' { 'ERROR' } - 'Unreachable' { 'ERROR' } - 'Skipped' { 'WARN' } - 'Warning' { 'WARN' } - default { 'INFO' } - } - Write-Log " $($g.Name): $($g.Count) server(ů) [$(($g.Group.Hostname) -join ', ')]" -Level $lvl -} - -$needRestart = @($results | Where-Object { $_.RequiresRestart }) -if ($needRestart.Count -gt 0) { - Write-Log '' - Write-Log "VYŽADOVÁN RESTART ($($needRestart.Count) server(ů)): $($needRestart.Hostname -join ', ')" -Level 'WARN' - Write-Log "Po restartu ověřit EventID 1808 v System logu nebo spustit:" - Write-Log " Invoke-Command -ComputerName -ScriptBlock { Get-WinEvent -FilterHashtable @{LogName='System';Id=@(1801,1808)} -MaxEvents 5 | Select TimeCreated,Id,Message }" -Level 'INFO' -} - -Write-Log ('=' * 60) -Write-Log "Set-SecureBootCertificateUpdate — KONEC | Log: $LogPath" -Write-Log ('=' * 60) - -# Vrátit výsledky jako objekty pro pipeline -$results - -#endregion