Background: What Is Expiring and Why It Matters
Secure Boot is a UEFI firmware security feature that ensures your device only boots software signed with trusted certificates. Since 2011, Microsoft has relied on a set of certificates embedded in device firmware to establish that trust chain. Those certificates — specifically the KEK CA 2011 and UEFI CA 2011 — have a hard expiration date of June 2026.
When a certificate expires, the firmware can no longer validate the boot manager signature. The practical result: devices will enter a Secure Boot violation state, which typically manifests as a boot failure, recovery key prompt, or in some firmware implementations, a complete inability to boot the OS.
Microsoft began addressing this with CVE-2023-24932 mitigations, delivering replacement certificates through Windows Update. The new certificates — Windows UEFI CA 2023 and an updated KEK — must be enrolled into the UEFI Secure Boot DB for the transition to be complete.
See Act Now: Secure Boot Certificates Expire in June 2026 and aka.ms/getsecureboot for Microsoft's official guidance. The enterprise deployment playbook is at KB5025885.
The Three Certificates Involved
| Certificate | Role | Expiration | Status |
|---|---|---|---|
| Microsoft Corporation KEK CA 2011 | Key Exchange Key — allows updating Secure Boot DB | June 2026 | EXPIRING |
| Microsoft UEFI CA 2011 | Signs third-party UEFI drivers & boot loaders | June 2026 | EXPIRING |
| Microsoft Windows Production PCA 2011 | Signs Windows boot manager binaries | October 2026 | EXPIRING |
| Windows UEFI CA 2023 | Replacement — signs new boot managers | 2048+ | REPLACEMENT |
| Microsoft KEK CA 2023 | Replacement KEK for DB management | 2048+ | REPLACEMENT |
The goal of this remediation is to enroll the 2023 certificates into the UEFI DB and ensure the device boots using the 2023-signed boot manager. The registry key UEFICA2023Status = "Updated" is Microsoft's authoritative indicator that the transition is complete.
The 6-Stage Compliance Model
The detection script uses a tiered model where each stage reflects the device's position in the certificate transition pipeline. Only Stage 5 is compliant (exit 0). All earlier stages trigger remediation (exit 1).
A common misconception: WindowsUEFICA2023Capable = 2 indicates the CA2023 key is in the UEFI DB and the device has booted from a 2023-signed boot manager, but Microsoft's WinCS tool writes UEFICA2023Status as the authoritative completion marker. Script v5.0 uses only UEFICA2023Status = "Updated" as the compliance gate.
Architecture & Flow Diagram
The diagram below illustrates how the detection and remediation scripts interact with Windows components, Intune, and the UEFI firmware layer.
Prerequisites & Device Requirements
Detection Script (v5.0)
This script evaluates the device against all 6 compliance stages and outputs a single human-readable status string to Intune. It collects rich diagnostic data locally at %ProgramData%\Microsoft\IntuneManagementExtension\Logs\SecureBootCertificateUpdate.log so you can troubleshoot any device remotely via log collection.
Key Detection Logic
- Stage 5 check is evaluated first — compliant devices exit immediately without expensive diagnostic queries.
- Fallback timer — after
FallbackDays(default 30) without compliance, the detection output flagsFallback:ACTIVEso the remediation script switches to the direct method. - Full registry dump, TPM, BitLocker, WU health are captured for non-compliant devices only.
- Event IDs 1036, 1043, 1044, 1045, 1795, 1801, 1808 are harvested from Kernel-Boot/Operational and System logs.
- WinCsFlags.exe /query output is captured when available.
#Requires -RunAsAdministrator <# .SYNOPSIS Detects whether Windows devices have completed the Secure Boot certificate transition before the June 2026 expiration. .DESCRIPTION Tiered compliance model (Stages 0–5). Only Stage 5 (UEFICA2023Status="Updated") is compliant. All other stages exit 1 and trigger the companion remediation script. Stage 0 – Secure Boot disabled Stage 1 – Deployment not triggered (AvailableUpdates not set, no timestamp) Stage 2 – Configured, awaiting Windows Update scan Stage 3 – Certificate update in progress Stage 4 – CA2023 in UEFI DB, reboot needed Stage 5 – COMPLIANT: UEFICA2023Status = "Updated" Collects rich diagnostics locally: TPM, BitLocker, WU health, pending reboots, full registry dump, Secure Boot event log, WinCS query output. .PARAMETER FallbackDays Days after managed opt-in before remediation falls back to direct method. Default: 30 .PARAMETER TimestampRegPath Registry path where ManagedOptInDate is stored. Default: HKLM:\SOFTWARE\MSEndpoint\Secureboot .NOTES Version : 5.0 Author : Souhaiel Morhag — msendpoint.com Modified : 2026-05-02 Blog : https://msendpoint.com #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [int]$FallbackDays = 30, [Parameter(Mandatory = $false)] [string]$TimestampRegPath = "HKLM:\SOFTWARE\MSEndpoint\Secureboot" ) #region ── LOGGING ────────────────────────────────────────────────────────── [string]$LogFile = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\SecureBootCertificateUpdate.log" [string]$ScriptName = "DETECT" [int]$MaxLogSizeMB = 4 $script:LogBuffer = [System.Collections.Generic.List[string]]::new() function Write-Log { param( [Parameter(Mandatory)][string]$Message, [ValidateSet("INFO","WARNING","ERROR","SUCCESS")] [string]$Level = "INFO" ) $script:LogBuffer.Add("$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [$ScriptName] [$Level] $Message") } function Flush-Log { if ($script:LogBuffer.Count -eq 0) { return } try { $dir = Split-Path $LogFile -Parent if (-not (Test-Path $dir)) { New-Item $dir -ItemType Directory -Force | Out-Null } if ((Test-Path $LogFile) -and ((Get-Item $LogFile).Length / 1MB -ge $MaxLogSizeMB)) { $backup = "$LogFile.old" if (Test-Path $backup) { Remove-Item $backup -Force -ErrorAction SilentlyContinue } Rename-Item $LogFile $backup -Force -ErrorAction SilentlyContinue } Add-Content -Path $LogFile -Value $script:LogBuffer.ToArray() -ErrorAction SilentlyContinue $script:LogBuffer.Clear() } catch {} } #endregion #region ── HELPERS ────────────────────────────────────────────────────────── function Get-SecureBootStatus { try { return (Confirm-SecureBootUEFI) } catch { try { $v = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\State" ` -Name UEFISecureBootEnabled -ErrorAction SilentlyContinue).UEFISecureBootEnabled return ($v -eq 1) } catch { return $false } } } function Get-FallbackStatus([string]$RegPath, [int]$Threshold) { $r = @{ TimestampExists = $false; OptInDate = $null; DaysElapsed = 0 DaysRemaining = $Threshold; IsActive = $false } try { if (Test-Path $RegPath) { $d = (Get-ItemProperty $RegPath -Name ManagedOptInDate -ErrorAction SilentlyContinue).ManagedOptInDate if ($d) { $p = [datetime]::Parse($d) $elapsed = ((Get-Date) - $p).TotalDays $r.TimestampExists = $true $r.OptInDate = $p.ToString("yyyy-MM-dd HH:mm:ss") $r.DaysElapsed = [math]::Floor($elapsed) $r.DaysRemaining = [math]::Max(0, $Threshold - [math]::Floor($elapsed)) $r.IsActive = ($elapsed -ge $Threshold) } } } catch {} return $r } function Get-SecureBootPayloadStatus { $path = "$env:SystemRoot\System32\SecureBootUpdates" $r = @{ FolderExists = $false; FileCount = 0; Files = @(); HasBinFiles = $false; IsHealthy = $false } try { if (Test-Path $path) { $r.FolderExists = $true $files = Get-ChildItem $path -File -ErrorAction SilentlyContinue if ($files) { $r.FileCount = $files.Count $r.Files = $files | ForEach-Object { "$($_.Name) ($([math]::Round($_.Length/1KB,1))KB)" } $r.HasBinFiles = ($files | Where-Object { $_.Extension -eq '.bin' }).Count -gt 0 $r.IsHealthy = $r.HasBinFiles } } } catch {} return $r } function Get-SecureBootTaskStatus { $r = @{ TaskExists = $false; LastRunTime = $null; LastTaskResult = $null NextRunTime = $null; ResultHex = $null; IsMissingFiles = $false } try { $task = Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI" ` -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue if ($task) { $r.TaskExists = $true $i = Get-ScheduledTaskInfo -TaskPath "\Microsoft\Windows\PI" ` -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue if ($i) { $r.LastRunTime = $i.LastRunTime $r.LastTaskResult = $i.LastTaskResult $r.ResultHex = "0x$($i.LastTaskResult.ToString('X'))" $r.NextRunTime = $i.NextRunTime $r.IsMissingFiles = ($i.LastTaskResult -eq 0x80070002) } } } catch {} return $r } #endregion #region ── MAIN ───────────────────────────────────────────────────────────── try { Write-Log "========== DETECTION STARTED ==========" Write-Log "Script Version: 5.0 | msendpoint.com" Write-Log "Computer: $env:COMPUTERNAME | User: $env:USERNAME" Write-Log "PowerShell: $($PSVersionTable.PSVersion) | $(if([Environment]::Is64BitProcess){'64-bit'}else{'32-bit'})" # ── Stage 0: Secure Boot enabled? ────────────────────────────────── $sbEnabled = Get-SecureBootStatus if (-not $sbEnabled) { Write-Log "Secure Boot DISABLED - Stage 0" -Level "ERROR" $sbStatePath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\State" if (Test-Path $sbStatePath) { Write-Log " Firmware: UEFI (SecureBoot State key exists but SB disabled in BIOS)" -Level "WARNING" Write-Log " ACTION: Enter BIOS/UEFI and enable Secure Boot under Security settings" -Level "WARNING" } else { Write-Log " Firmware: Legacy BIOS detected (no SecureBoot State key)" -Level "ERROR" Write-Log " ACTION: Convert disk to GPT and switch from Legacy to UEFI firmware mode" -Level "ERROR" } try { $bios = Get-CimInstance Win32_BIOS -ErrorAction SilentlyContinue if ($bios) { Write-Log " BIOS: $($bios.Manufacturer) $($bios.SMBIOSBIOSVersion) ($($bios.ReleaseDate))" } } catch {} Write-Host "SECURE_BOOT_DISABLED | Action: Enable Secure Boot in BIOS/UEFI" Write-Log "Detection Result: NON-COMPLIANT - Stage 0 (exit 1)" -Level "WARNING" Write-Log "========== DETECTION COMPLETED ==========" Flush-Log; exit 1 } Write-Log "Secure Boot: ENABLED" -Level "SUCCESS" # ── Stage 1: Deployment triggered? ───────────────────────────────── $regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Secureboot" $availableUpdates = if (Test-Path $regPath) { (Get-ItemProperty $regPath -Name AvailableUpdates -ErrorAction SilentlyContinue).AvailableUpdates } $deploymentTS = if (Test-Path $TimestampRegPath) { (Get-ItemProperty $TimestampRegPath -Name ManagedOptInDate -ErrorAction SilentlyContinue).ManagedOptInDate } $deploymentTriggered = ($null -ne $availableUpdates -and $availableUpdates -ne 0) -or ($null -ne $deploymentTS) if (-not $deploymentTriggered) { # Pre-check: maybe already compliant without being triggered by us $earlyStatus = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing" ` -Name UEFICA2023Status -ErrorAction SilentlyContinue).UEFICA2023Status if ($earlyStatus -eq "Updated") { Write-Host "COMPLIANT | Deployment:NotTriggered | UEFICA2023Status=Updated" Write-Log "Already compliant (UEFICA2023Status=Updated) without our trigger" -Level "SUCCESS" Write-Log "Detection Result: COMPLIANT - Stage 5 (exit 0)" -Level "SUCCESS" Flush-Log; exit 0 } Write-Log "Deployment NOT triggered - Stage 1 (remediation will set 0x5944)" -Level "WARNING" Write-Host "DEPLOYMENT_NOT_TRIGGERED | Action: Remediation will set AvailableUpdates" Write-Log "Detection Result: NON-COMPLIANT - Stage 1 (exit 1)" -Level "WARNING" Write-Log "========== DETECTION COMPLETED ==========" Flush-Log; exit 1 } # ── Stage 5: COMPLIANT ────────────────────────────────────────────── $servicingPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing" $uefiStatus = (Get-ItemProperty $servicingPath -Name UEFICA2023Status -ErrorAction SilentlyContinue).UEFICA2023Status $ca2023Capable = (Get-ItemProperty $servicingPath -Name WindowsUEFICA2023Capable -ErrorAction SilentlyContinue).WindowsUEFICA2023Capable $uefiError = (Get-ItemProperty $servicingPath -Name UEFICA2023Error -ErrorAction SilentlyContinue).UEFICA2023Error $uefiErrorEvent = (Get-ItemProperty $servicingPath -Name UEFICA2023ErrorEvent -ErrorAction SilentlyContinue).UEFICA2023ErrorEvent $fallback = Get-FallbackStatus -RegPath $TimestampRegPath -Threshold $FallbackDays $payload = Get-SecureBootPayloadStatus $taskStatus = Get-SecureBootTaskStatus $winCsAvailable = Test-Path "$env:SystemRoot\System32\WinCsFlags.exe" # Build detail string $dp = @() if ($null -ne $uefiStatus) { $dp += "Status:$uefiStatus" } if ($null -ne $ca2023Capable) { $capText = switch($ca2023Capable){0{"NotInDB"} 1{"InDB"} 2{"InDB+Booting2023"} default{"Unknown"}} $dp += "CA2023:$capText" } if ($null -ne $uefiError -and $uefiError -ne 0) { $dp += "Error:0x$($uefiError.ToString('X'))" } if (-not $payload.IsHealthy) { $dp += "Payload:MISSING" } if ($taskStatus.IsMissingFiles) { $dp += "Task:0x80070002" } if ($winCsAvailable) { $dp += "WinCS:Available" } if ($fallback.TimestampExists) { $dp += if ($fallback.IsActive) { "Fallback:ACTIVE($($fallback.DaysElapsed)d)" } else { "Fallback:$($fallback.DaysRemaining)d remaining" } } $details = $dp -join " | " if ($uefiStatus -eq "Updated") { Write-Log "UEFICA2023Status=Updated — COMPLIANT (Stage 5)" -Level "SUCCESS" if (Test-Path $TimestampRegPath) { try { Remove-Item $TimestampRegPath -Recurse -Force -ErrorAction Stop Write-Log "Cleanup: $TimestampRegPath removed" -Level "SUCCESS" } catch { Write-Log "Cleanup failed: $($_.Exception.Message)" -Level "WARNING" } } Write-Host "COMPLIANT | $details" Write-Log "Detection Result: COMPLIANT - Stage 5 (exit 0)" -Level "SUCCESS" Write-Log "========== DETECTION COMPLETED ==========" Flush-Log; exit 0 } # ── Collect diagnostics (non-compliant only) ──────────────────────── Write-Log "---------- DIAGNOSTIC DATA ----------" $os = Get-CimInstance Win32_OperatingSystem $lastBoot = $os.LastBootUpTime $uptime = (Get-Date) - $lastBoot Write-Log "OS: $($os.Caption) (Build $($os.BuildNumber))" Write-Log "Last Boot: $($lastBoot.ToString('yyyy-MM-dd HH:mm:ss')) | Uptime: $([math]::Floor($uptime.TotalDays))d $($uptime.Hours)h" # TPM try { $tpm = Get-CimInstance -Namespace "root\cimv2\Security\MicrosoftTpm" -ClassName Win32_Tpm -ErrorAction Stop Write-Log "TPM: Enabled=$($tpm.IsEnabled_InitialValue) Activated=$($tpm.IsActivated_InitialValue) Spec=$($tpm.SpecVersion)" } catch { Write-Log "TPM: $($_.Exception.Message)" -Level "WARNING" } # BitLocker try { $bl = Get-CimInstance -Namespace "root\cimv2\Security\MicrosoftVolumeEncryption" ` -ClassName Win32_EncryptableVolume -Filter "DriveLetter='$env:SystemDrive'" -ErrorAction Stop $protect = switch($bl.ProtectionStatus){0{"OFF"} 1{"ON"} 2{"UNKNOWN"} default{"?"}} Write-Log "BitLocker: Protection=$protect | Status=$(switch($bl.ConversionStatus){0{'Decrypted'} 1{'Encrypted'} default{$bl.ConversionStatus}})" if ($protect -eq "ON") { Write-Log "BitLocker NOTE: Cert changes may trigger recovery key prompt on next reboot" -Level "WARNING" } } catch { Write-Log "BitLocker: $($_.Exception.Message)" -Level "WARNING" } # Pending reboots $rebootReasons = @() if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { $rebootReasons += "CBS" } if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { $rebootReasons += "WU" } Write-Log "Pending Reboot: $(if($rebootReasons.Count -gt 0){"YES - $($rebootReasons -join ',')"}else{"No"})" # Event log harvest try { $evts = Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Kernel-Boot/Operational','System'; Id=1036,1043,1044,1045,1795,1801,1808} -MaxEvents 20 -ErrorAction SilentlyContinue if ($evts) { $evts | Group-Object Id | ForEach-Object { $l = $_.Group | Sort-Object TimeCreated -Descending | Select-Object -First 1 Write-Log "Event $($_.Name): Last=$($l.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')) Count=$($_.Count)" }} } catch {} Write-Log "---------- END DIAGNOSTICS ----------" # ── Stage 4: CA2023 in DB, reboot pending ─────────────────────────── if ($ca2023Capable -eq 1) { Write-Log "Stage 4: CA2023 cert in DB but reboot not yet taken" -Level "WARNING" Write-Host "CONFIGURED_CA2023_IN_DB | $details | Action: Reboot to complete transition" Write-Log "Detection Result: NON-COMPLIANT - Stage 4 (exit 1)" -Level "WARNING" Write-Log "========== DETECTION COMPLETED ==========" Flush-Log; exit 1 } # ── Stage 3: WU actively changing bits ────────────────────────────── if ($null -ne $availableUpdates -and $availableUpdates -ne 0 -and $availableUpdates -ne 22852) { if ($availableUpdates -eq 0x4104) { Write-Log "STUCK STATE 0x4104: KEK bit not clearing - check OEM firmware update" -Level "ERROR" } Write-Host "CONFIGURED_UPDATE_IN_PROGRESS | $details | Action: Waiting for Windows Update" Write-Log "Detection Result: NON-COMPLIANT - Stage 3 (exit 1)" -Level "WARNING" Write-Log "========== DETECTION COMPLETED ==========" Flush-Log; exit 1 } # ── Stage 2: Configured, awaiting WU ──────────────────────────────── Write-Log "Stage 2: Deployment triggered, waiting for WU scan to begin processing" -Level "WARNING" Write-Host "CONFIGURED_AWAITING_UPDATE | $details | Action: Waiting for Windows Update scan" Write-Log "Detection Result: NON-COMPLIANT - Stage 2 (exit 1)" -Level "WARNING" Write-Log "========== DETECTION COMPLETED ==========" Flush-Log; exit 1 } catch { Write-Log "Unexpected error: $($_.Exception.Message)" -Level "ERROR" Write-Log "Stack: $($_.ScriptStackTrace)" -Level "ERROR" Write-Host "ERROR: $($_.Exception.Message)" Flush-Log; exit 1 } #endregion
Remediation Script (v5.0)
When the detection script exits 1, Intune runs the remediation script. This script is idempotent — it checks whether deployment was already triggered and only performs work when needed. It includes a firmware age gate that blocks remediation on devices with firmware older than one year, preventing potential boot failures.
Remediation Priority Order
- First run: Sets
AvailableUpdates = 0x5944and triggers theSecure-Boot-Updatetask immediately. Writes aManagedOptInDatetimestamp. - Subsequent runs (within FallbackDays): Already configured — exits 0 with countdown to fallback.
- Fallback (after FallbackDays): Tries WinCsFlags.exe /apply (preferred, no payload dependency). Falls back to legacy step-by-step
AvailableUpdatesmethod if WinCS unavailable and payload files exist. - Blocked: Exits 1 if firmware too old, Secure Boot disabled, or no WinCS and no payload files.
#Requires -RunAsAdministrator <# .SYNOPSIS Remediates Windows devices for Secure Boot certificate transition (June 2026 expiration). .DESCRIPTION Deploys Secure Boot certificate updates using AvailableUpdates = 0x5944 (Option 2). Includes firmware age gate, idempotency checks, WinCS preferred fallback, and BitLocker awareness. .PARAMETER FallbackDays Days after opt-in before switching to direct fallback method. Default: 30 .PARAMETER TimestampRegPath Registry path for ManagedOptInDate timestamp. Default: HKLM:\SOFTWARE\MSEndpoint\Secureboot .NOTES Version : 5.0 Author : Souhaiel Morhag — msendpoint.com Modified : 2026-05-02 Blog : https://msendpoint.com #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [int]$FallbackDays = 30, [Parameter(Mandatory = $false)] [string]$TimestampRegPath = "HKLM:\SOFTWARE\MSEndpoint\Secureboot" ) #region ── LOGGING ────────────────────────────────────────────────────────── [string]$LogFile = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\SecureBootCertificateUpdate.log" [string]$ScriptName = "REMEDIATE" [int]$MaxLogSizeMB = 4 $script:LogBuffer = [System.Collections.Generic.List[string]]::new() function Write-Log { param([Parameter(Mandatory)][string]$Message, [ValidateSet("INFO","WARNING","ERROR","SUCCESS")][string]$Level = "INFO") $script:LogBuffer.Add("$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [$ScriptName] [$Level] $Message") } function Flush-Log { if ($script:LogBuffer.Count -eq 0) { return } try { $dir = Split-Path $LogFile -Parent if (-not (Test-Path $dir)) { New-Item $dir -ItemType Directory -Force | Out-Null } if ((Test-Path $LogFile) -and ((Get-Item $LogFile).Length / 1MB -ge $MaxLogSizeMB)) { $b = "$LogFile.old" if (Test-Path $b) { Remove-Item $b -Force -ErrorAction SilentlyContinue } Rename-Item $LogFile $b -Force -ErrorAction SilentlyContinue } Add-Content -Path $LogFile -Value $script:LogBuffer.ToArray() -ErrorAction SilentlyContinue $script:LogBuffer.Clear() } catch {} } #endregion #region ── HELPERS ────────────────────────────────────────────────────────── function Get-SecureBootStatus { try { return (Confirm-SecureBootUEFI) } catch { try { $v = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\State" ` -Name UEFISecureBootEnabled -ErrorAction SilentlyContinue).UEFISecureBootEnabled return ($v -eq 1) } catch { return $false } } } function Get-FirmwareAgeStatus { $r = @{ Manufacturer = "Unknown"; Model = "Unknown"; BiosVersion = "Unknown" ReleaseDate = $null; AgeDays = $null; IsStale = $false UpdateGuidance = "Check your device manufacturer's support site for firmware updates." } try { $bios = Get-CimInstance Win32_BIOS -ErrorAction Stop $cs = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop $r.Manufacturer = if ($cs.Manufacturer) { $cs.Manufacturer.Trim() } else { "Unknown" } $r.Model = if ($cs.Model) { $cs.Model.Trim() } else { "Unknown" } $r.BiosVersion = $bios.SMBIOSBIOSVersion if ($null -ne $bios.ReleaseDate) { $r.ReleaseDate = $bios.ReleaseDate $r.AgeDays = [math]::Floor(((Get-Date) - $bios.ReleaseDate).TotalDays) $r.IsStale = ($r.AgeDays -gt 365) } switch -Wildcard ($r.Manufacturer.ToUpper()) { "LENOVO*" { $r.UpdateGuidance = "Update via Lenovo Vantage or https://support.lenovo.com" } "DELL*" { $r.UpdateGuidance = "Update via Dell Command Update or https://www.dell.com/support" } { $_ -like "HP*" -or $_ -like "HEWLETT*" } { $r.UpdateGuidance = "Update via HP Support Assistant or https://support.hp.com" } "MICROSOFT*" { $r.UpdateGuidance = "Update via Windows Update (Surface Drivers)" } } } catch {} return $r } function Get-SecureBootPayloadStatus { $path = "$env:SystemRoot\System32\SecureBootUpdates" $r = @{ FolderExists = $false; FileCount = 0; HasBinFiles = $false; IsHealthy = $false } try { if (Test-Path $path) { $r.FolderExists = $true $files = Get-ChildItem $path -File -ErrorAction SilentlyContinue if ($files) { $r.FileCount = $files.Count $r.HasBinFiles = ($files | Where-Object Extension -eq '.bin').Count -gt 0 $r.IsHealthy = $r.HasBinFiles } } } catch {} return $r } function Invoke-SecureBootTask([int]$TimeoutSec = 60) { Start-ScheduledTask -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction Stop Write-Log "Secure-Boot-Update task triggered" -Level "SUCCESS" $deadline = (Get-Date).AddSeconds($TimeoutSec) do { Start-Sleep -Seconds 2 $state = (Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue).State } while ($state -eq 'Running' -and (Get-Date) -lt $deadline) $info = Get-ScheduledTaskInfo -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue return $info.LastTaskResult } #endregion #region ── MAIN ───────────────────────────────────────────────────────────── try { Write-Log "========== REMEDIATION STARTED ==========" Write-Log "Script Version: 5.0 | msendpoint.com" Write-Log "Computer: $env:COMPUTERNAME | User: $env:USERNAME" Write-Log "PowerShell: $($PSVersionTable.PSVersion) | $(if([Environment]::Is64BitProcess){'64-bit'}else{'32-bit'})" # ── Secure Boot gate ─────────────────────────────────────────────── if (-not (Get-SecureBootStatus)) { Write-Log "Secure Boot DISABLED - cannot remediate" -Level "ERROR" Write-Host "FAILED: Secure Boot DISABLED - Enable in BIOS/UEFI manually" Flush-Log; exit 1 } Write-Log "Secure Boot: ENABLED" -Level "SUCCESS" # ── Firmware age gate (>365 days = block) ────────────────────────── $fw = Get-FirmwareAgeStatus Write-Log "Firmware: $($fw.Manufacturer) $($fw.Model) | BIOS: $($fw.BiosVersion) | Age: $($fw.AgeDays) days" if ($fw.IsStale) { Write-Log "BLOCKED: Firmware $($fw.AgeDays)d old (>365) - $($fw.UpdateGuidance)" -Level "ERROR" Write-Host "BLOCKED_FIRMWARE_STALE: $($fw.Manufacturer) $($fw.Model) | Age: $($fw.AgeDays)d | $($fw.UpdateGuidance)" Flush-Log; exit 1 } Write-Log "Firmware age gate: PASSED" -Level "SUCCESS" # ── Pre-check UEFICA2023Error ─────────────────────────────────────── $servicingPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing" $uefiError = (Get-ItemProperty $servicingPath -Name UEFICA2023Error -ErrorAction SilentlyContinue).UEFICA2023Error $uefiErrorEvent = (Get-ItemProperty $servicingPath -Name UEFICA2023ErrorEvent -ErrorAction SilentlyContinue).UEFICA2023ErrorEvent if ($null -ne $uefiError -and $uefiError -ne 0) { Write-Log "WARNING: UEFICA2023Error=0x$($uefiError.ToString('X')) | Event=$uefiErrorEvent" -Level "WARNING" Write-Log " Ref: https://support.microsoft.com/topic/37e47cf8-608b-4a87-8175-bdead630eb69" -Level "WARNING" } # ── Compliance check (already done?) ─────────────────────────────── $uefiStatus = (Get-ItemProperty $servicingPath -Name UEFICA2023Status -ErrorAction SilentlyContinue).UEFICA2023Status $ca2023Capable = (Get-ItemProperty $servicingPath -Name WindowsUEFICA2023Capable -ErrorAction SilentlyContinue).WindowsUEFICA2023Capable if ($uefiStatus -eq "Updated") { Write-Log "Device ALREADY COMPLIANT (UEFICA2023Status=Updated)" -Level "SUCCESS" if (Test-Path $TimestampRegPath) { try { Remove-Item $TimestampRegPath -Recurse -Force -ErrorAction Stop Write-Log "Cleanup: $TimestampRegPath removed" -Level "SUCCESS" } catch {} } Write-Host "ALREADY_COMPLIANT: UEFICA2023Status=Updated. Secure Boot certificate transition complete." Flush-Log; exit 0 } # ── Main registry config ──────────────────────────────────────────── $regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Secureboot" $regValue = 0x5944 $existingAU = if (Test-Path $regPath) { (Get-ItemProperty $regPath -Name AvailableUpdates -ErrorAction SilentlyContinue).AvailableUpdates } $existingTS = if (Test-Path $TimestampRegPath) { (Get-ItemProperty $TimestampRegPath -Name ManagedOptInDate -ErrorAction SilentlyContinue).ManagedOptInDate } $alreadyConfigured = ($null -ne $existingAU -and $existingAU -ne 0) -or ($null -ne $existingTS) if ($alreadyConfigured) { Write-Log "Deployment already triggered (AU=$(if($null -ne $existingAU){"0x$($existingAU.ToString('X'))"}else{'cleared'}) | TS=$existingTS)" # Accelerate if AU missing (legacy CFR path) if ($null -eq $existingAU -or $existingAU -eq 0) { if (-not (Test-Path $regPath)) { New-Item $regPath -Force | Out-Null } Set-ItemProperty $regPath AvailableUpdates $regValue -Type DWord -Force Write-Log "AvailableUpdates reset to 0x$($regValue.ToString('X')) to accelerate" -Level "INFO" $task = Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue if ($task) { Start-ScheduledTask -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue } } # Backfill timestamp if missing if (-not $existingTS) { if (-not (Test-Path $TimestampRegPath)) { New-Item $TimestampRegPath -Force | Out-Null } Set-ItemProperty $TimestampRegPath ManagedOptInDate ((Get-Date).ToString("o")) -Type String -Force Write-Log "ManagedOptInDate backfilled - fallback clock starts now" Write-Host "ALREADY_CONFIGURED: Deployment active. Fallback timer started." Flush-Log; exit 0 } # Fallback timer evaluation $optInDate = [datetime]::Parse($existingTS) $daysElapsed = [math]::Floor(((Get-Date) - $optInDate).TotalDays) $daysRemaining = [math]::Max(0, $FallbackDays - $daysElapsed) Write-Log "Fallback: $daysElapsed/$FallbackDays days | Remaining: $daysRemaining" if ($daysElapsed -lt $FallbackDays) { $capText = switch($ca2023Capable){0{"NotInDB"} 1{"InDB"} 2{"InDB+Booting2023"} default{"Pending"}} Write-Host "ALREADY_CONFIGURED: CA2023=$capText. Fallback in $($daysRemaining)d." Flush-Log; exit 0 } # ── FALLBACK ACTIVATED ────────────────────────────────────────── Write-Log "=== FALLBACK ACTIVATED: $daysElapsed days elapsed ===" -Level "WARNING" $winCsPath = "$env:SystemRoot\System32\WinCsFlags.exe" if (Test-Path $winCsPath) { Write-Log "WinCS method: AVAILABLE (preferred)" -Level "SUCCESS" try { $out = & $winCsPath /apply --key "F33E0C8E002" 2>&1 ($out | Out-String).Trim() -split "`n" | Where-Object { $_.Trim() } | ForEach-Object { Write-Log "WinCS: $($_.Trim())" } $task = Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue if ($task) { Start-ScheduledTask -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue } Write-Host "FALLBACK_WINCS: Key F33E0C8E002 applied. Reboot required." Flush-Log; exit 0 } catch { Write-Log "WinCS FAILED: $($_.Exception.Message) - trying legacy path" -Level "ERROR" } } # Legacy AvailableUpdates path (requires payload + task) $payload = Get-SecureBootPayloadStatus $task = Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue if (-not $payload.IsHealthy -or -not $task) { $reason = if (-not $payload.IsHealthy) { "No payload files" } else { "Task not found" } Write-Log "FALLBACK BLOCKED: $reason - install latest cumulative update" -Level "ERROR" Write-Host "FALLBACK_BLOCKED: $reason. Install latest cumulative update to proceed." Flush-Log; exit 1 } Write-Log "Legacy fallback: AvailableUpdates step-by-step (KB5025885)" $ok = $true if ($null -eq $ca2023Capable -or $ca2023Capable -lt 1) { Set-ItemProperty $regPath AvailableUpdates 0x40 -Type DWord -Force -ErrorAction Stop $r1 = Invoke-SecureBootTask if ($r1 -eq 0x80070002) { Write-Log "Step1 Task failed 0x80070002 - payload missing at runtime" -Level "ERROR" $ok = $false } } if ($ok) { Set-ItemProperty $regPath AvailableUpdates 0x100 -Type DWord -Force -ErrorAction Stop $r2 = Invoke-SecureBootTask if ($r2 -eq 0x80070002) { Write-Log "Step2 Task failed 0x80070002" -Level "ERROR"; $ok = $false } } if ($ok) { Write-Host "FALLBACK_APPLIED: Direct method triggered. Reboot may be required." Flush-Log; exit 0 } else { Write-Host "FALLBACK_FAILED: Task errors encountered. Check log." Flush-Log; exit 1 } } # ── First-time deployment ─────────────────────────────────────────── Write-Log "First deployment: setting AvailableUpdates = 0x$($regValue.ToString('X'))" if (-not (Test-Path $regPath)) { New-Item $regPath -Force | Out-Null } Set-ItemProperty $regPath AvailableUpdates $regValue -Type DWord -Force -ErrorAction Stop $verify = (Get-ItemProperty $regPath AvailableUpdates -ErrorAction Stop).AvailableUpdates if ($verify -ne $regValue) { Write-Log "Verification failed: Expected 0x$($regValue.ToString('X')), got 0x$($verify.ToString('X'))" -Level "ERROR" Write-Host "FAILED: Registry mismatch" Flush-Log; exit 1 } Write-Log "AvailableUpdates = 0x$($verify.ToString('X')) verified" -Level "SUCCESS" # Trigger task immediately $task = Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue if ($task) { Start-ScheduledTask -TaskPath "\Microsoft\Windows\PI" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue Write-Log "Secure-Boot-Update task triggered" -Level "SUCCESS" } else { Write-Log "Task not found - will process on next 12h WU cycle" } # Write fallback timer timestamp if (-not (Test-Path $TimestampRegPath)) { New-Item $TimestampRegPath -Force | Out-Null } Set-ItemProperty $TimestampRegPath ManagedOptInDate ((Get-Date).ToString("o")) -Type String -Force Write-Log "Fallback timer started (threshold: $FallbackDays days)" Write-Host "SUCCESS: AvailableUpdates=0x$($verify.ToString('X')). Certificate deployment initiated." Write-Log "Remediation Result: SUCCESS (exit 0)" -Level "SUCCESS" Write-Log "========== REMEDIATION COMPLETED ==========" Flush-Log; exit 0 } catch { Write-Log "Unexpected error: $($_.Exception.Message)" -Level "ERROR" Write-Log "Stack: $($_.ScriptStackTrace)" -Level "ERROR" Write-Host "ERROR: $($_.Exception.Message)" Flush-Log; exit 1 } #endregion
Deploying via Intune Proactive Remediations
Secure Boot Certificate Update 2026. Publisher: your org. Description: note the June 2026 deadline and KB5025885 reference.Detect-SecureBootCertificateUpdate.ps1 as the detection script and Remediate-SecureBootCertificateUpdate.ps1 as the remediation script. Set run context to System (64-bit). Enable "Run script in 64-bit PowerShell".COMPLIANT, CONFIGURED_AWAITING_UPDATE, CONFIGURED_CA2023_IN_DB, etc.Monitoring & Reporting
The detection script outputs standardized status strings to Intune's remediation output field. You can filter and track devices by stage using the Intune portal or by collecting logs via Remote Help / Log collection.
| Output String | Stage | Action Required | Status |
|---|---|---|---|
COMPLIANT | ... |
5 | None — transition complete | ✓ Done |
SECURE_BOOT_DISABLED | ... |
0 | Manual BIOS/UEFI enablement | Manual |
DEPLOYMENT_NOT_TRIGGERED | ... |
1 | Remediation runs automatically | Auto-fix |
CONFIGURED_AWAITING_UPDATE | ... |
2 | Wait for WU scan (up to 14 days) | Waiting |
CONFIGURED_UPDATE_IN_PROGRESS | ... |
3 | Wait for WU to process cert bits | In Progress |
CONFIGURED_CA2023_IN_DB | ... |
4 | Reboot device | Reboot needed |
BLOCKED_FIRMWARE_STALE | ... |
— | Update OEM firmware | Blocked |
FALLBACK_WINCS | ... |
— | WinCS applied — reboot needed | Fallback |
For local troubleshooting on individual devices, collect the IME log at %ProgramData%\Microsoft\IntuneManagementExtension\Logs\SecureBootCertificateUpdate.log. The detection script writes detailed stage analysis, registry dumps, event log entries, and WinCS output to this file on every non-compliant run.
Troubleshooting Common Issues
Task error 0x80070002 (ERROR_FILE_NOT_FOUND)
The Secure-Boot-Update scheduled task returned 0x80070002. This means the payload folder %SystemRoot%\System32\SecureBootUpdates\ is missing or empty. Fix: Install the latest cumulative update. Once the payload files are present, the task will succeed on its next run, or the remediation script will trigger it again via the fallback path using WinCsFlags.exe if available.
Stuck at AvailableUpdates = 0x4104
The 0x4104 value indicates that the KEK certificate bit (0x0004) is not clearing. This is a firmware compatibility issue. Fix: Contact your OEM for a firmware update that supports the new KEK CA 2023 certificate. See Microsoft's Secure Boot key management guidance for hardware partner requirements.
BLOCKED_FIRMWARE_STALE — Firmware older than 1 year
The remediation script blocks devices whose firmware is more than 365 days old. Applying Secure Boot certificate changes on outdated firmware can cause boot failures. Update the firmware using your OEM's tool (Lenovo Vantage, Dell Command Update, HP Support Assistant, or Windows Update for Surface), then re-run the remediation.
BitLocker Recovery Key Prompts
Changes to Secure Boot policy (new certificates in DB) can cause BitLocker to request a recovery key on the next reboot. Before deployment: ensure all BitLocker recovery keys are escrowed to Entra ID. You can verify with the PowerShell command: Get-BitLockerVolume | Select-Object -ExpandProperty KeyProtector and confirm via the Intune device encryption report.
WinCsFlags.exe not available
WinCsFlags.exe is included in the October/November 2025 and later cumulative updates. If the device doesn't have this tool, the fallback will attempt the legacy step-by-step AvailableUpdates method provided the payload files are present. Install the latest quality update to get WinCsFlags.exe on all devices.
1. Immediately: Deploy the Proactive Remediation to all Windows devices.
2. This week: Ensure BitLocker recovery keys are escrowed to Entra ID.
3. This week: Push the latest cumulative update to all devices (gets WinCsFlags.exe).
4. Ongoing: Monitor stage progression in Intune Reports. Target 100% Stage 5 before June 2026.
5. Exceptions: Identify firmware-stale and Secure Boot disabled devices — escalate for manual remediation.