Detect-SecureBootCA2023.ps1

<#PSScriptInfo
.VERSION 2.0
.GUID 7f4c2c9b-1d3e-4b8a-9f7c-92c4e7d1a6b2
.AUTHOR Mert Efe Kanlikilic
.COMPANYNAME mertefekanlikilic.com
.COPYRIGHT (c) 2026 Mert Efe Kanlikilic
.TAGS SecureBoot UEFI Intune Compliance Detection PCA2023
.LICENSEURI https://github.com/mertefekanlikilic
.PROJECTURI https://github.com/mertefekanlikilic
.DESCRIPTION Secure Boot CA 2023 certificate update detection script for Intune compliance.
#>


<#
.SYNOPSIS
    Secure Boot CA 2023 certificate update detection script.

.NOTES
    Author : Mert Efe Kanlikilic -- mertefekanlikilic.com
    Version : 2.0
    Platform: Windows 11 | Secure Boot enabled devices
    Run as : SYSTEM, 64-bit PowerShell
    Date : May 2026

    Registry paths:
      HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing
        UEFICA2023Status : NotStarted | InProgress | Updated
        WindowsUEFICA2023Capable : 0 = insufficient, 1 = in progress, 2 = complete

      HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot
        AvailableUpdates : 0x5944 = opted in
        RemediationTimestamp : First remediation date (written by remediation script)

    UEFI DB thumbprint:
      Microsoft Windows Production PCA 2023
      Thumbprint: 45 66 52 45 2A 1B 5A 25 67 48 7F 81 B1 3F 1F E2
#>



$LOG_PATH           = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\SecureBoot-Detection.log"
$EVENTLOG_SOURCE    = "SecureBootCA2023"
$EVENTLOG_LOG       = "Application"
$SERVICING_KEY      = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing"
$SECUREBOOT_KEY     = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot"
$FAILED_DAYS        = 7
$PCA2023_THUMBPRINT = "45 66 52 45 2A 1B 5A 25 67 48 7F 81 B1 3F 1F E2"

function Write-Log {
    param([string]$Message, [ValidateSet("INFO","WARN","ERROR")]$Level = "INFO")
    $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    try { Add-Content -Path $LOG_PATH -Value "[$ts][$Level] $Message" -Encoding UTF8 } catch { }
}

function Write-EventEntry {
    param([string]$Message, [int]$EventId, [string]$EntryType = "Information")
    try {
        if (-not [System.Diagnostics.EventLog]::SourceExists($EVENTLOG_SOURCE)) {
            New-EventLog -LogName $EVENTLOG_LOG -Source $EVENTLOG_SOURCE -ErrorAction Stop
        }
        Write-EventLog -LogName $EVENTLOG_LOG -Source $EVENTLOG_SOURCE `
            -EventId $EventId -EntryType $EntryType -Message $Message
    }
    catch {
        Write-Log "Event log write failed: $($_.Exception.Message)" -Level WARN
    }
}

function Write-ComplianceResult {
    param([string]$State, [string]$Message, [int]$ExitCode)

    $output   = "${State}: ${Message}"
    $eventMap = @{
        "Compliant"          = @{ Id = 1000; Type = "Information" }
        "PendingRemediation" = @{ Id = 1001; Type = "Warning" }
        "PendingRestart"     = @{ Id = 1002; Type = "Warning" }
        "Failed"             = @{ Id = 1003; Type = "Error" }
        "ManualReview"       = @{ Id = 1004; Type = "Error" }
        "NotApplicable"      = @{ Id = 1005; Type = "Information" }
    }

    Write-Host $output
    Write-Log  $output

    if ($eventMap.ContainsKey($State)) {
        Write-EventEntry -Message $output -EventId $eventMap[$State].Id `
            -EntryType $eventMap[$State].Type
    }

    Stop-Transcript -ErrorAction SilentlyContinue
    exit $ExitCode
}

function Get-RegistryState {
    $result = [PSCustomObject]@{
        UEFICA2023Status         = "Unknown"
        WindowsUEFICA2023Capable = -1
        AvailableUpdates         = -1
        RemediationTimestamp     = $null
        Layer1Pass               = $false
        State                    = "Unknown"
        Reason                   = ""
    }

    try {
        $serv = Get-ItemProperty -Path $SERVICING_KEY -ErrorAction Stop
        $result.UEFICA2023Status         = $serv.UEFICA2023Status
        $result.WindowsUEFICA2023Capable = [int]($serv.WindowsUEFICA2023Capable)
    }
    catch {
        $result.State  = "PendingRemediation"
        $result.Reason = "Servicing key not found -- update has never started"
        return $result
    }

    try {
        $sb = Get-ItemProperty -Path $SECUREBOOT_KEY -ErrorAction SilentlyContinue
        if ($sb.AvailableUpdates)     { $result.AvailableUpdates     = $sb.AvailableUpdates }
        if ($sb.RemediationTimestamp) { $result.RemediationTimestamp = [datetime]$sb.RemediationTimestamp }
    }
    catch { }

    switch ($result.UEFICA2023Status) {
        "Updated" {
            if ($result.WindowsUEFICA2023Capable -eq 2) {
                $result.Layer1Pass = $true
                $result.State  = "Layer1Pass"
                $result.Reason = "Registry complete (Status=Updated, Capable=2) -- UEFI DB verification pending"
            }
            else {
                $result.State  = "ManualReview"
                $result.Reason = "Status=Updated but Capable=$($result.WindowsUEFICA2023Capable) -- inconsistent state"
            }
        }
        "InProgress" {
            if ($result.RemediationTimestamp) {
                $daysSince = ((Get-Date) - $result.RemediationTimestamp).TotalDays
                if ($daysSince -ge $FAILED_DAYS) {
                    $result.State  = "Failed"
                    $result.Reason = "InProgress for $([math]::Round($daysSince,1)) days -- remediation appears stuck"
                }
                else {
                    $result.State  = "PendingRestart"
                    $result.Reason = "Update in progress -- restart pending ($([math]::Round($daysSince,1)) days)"
                }
            }
            else {
                $result.State  = "PendingRestart"
                $result.Reason = "Update in progress (InProgress) -- restart pending"
            }
        }
        "NotStarted" {
            $result.State  = "PendingRemediation"
            $result.Reason = "Update has not started (NotStarted)"
        }
        default {
            $result.State  = "PendingRemediation"
            $result.Reason = "Unknown status: $($result.UEFICA2023Status)"
        }
    }

    return $result
}

function Test-UEFIDatabase {
    try {
        $dbVar = Get-SecureBootUEFI -Name "db" -ErrorAction Stop

        if ($null -eq $dbVar -or $null -eq $dbVar.Bytes) {
            Write-Log "UEFI db variable empty or unreadable" -Level WARN
            return "Unavailable"
        }

        $targetBytes = ($PCA2023_THUMBPRINT -split " ") | ForEach-Object { [Convert]::ToByte($_, 16) }
        $dbBytes     = $dbVar.Bytes
        $dbLen       = $dbBytes.Length
        $tLen        = $targetBytes.Length
        $found       = $false

        for ($i = 0; $i -le ($dbLen - $tLen); $i++) {
            $match = $true
            for ($j = 0; $j -lt $tLen; $j++) {
                if ($dbBytes[$i + $j] -ne $targetBytes[$j]) { $match = $false; break }
            }
            if ($match) { $found = $true; break }
        }

        if ($found) {
            Write-Log "UEFI DB: PCA2023 thumbprint verified"
            return "Verified"
        }
        else {
            Write-Log "UEFI DB: PCA2023 thumbprint NOT FOUND -- ManualReview required" -Level WARN
            return "Missing"
        }
    }
    catch {
        Write-Log "Get-SecureBootUEFI error: $($_.Exception.Message) -- skipping Layer 2" -Level WARN
        return "Unavailable"
    }
}

# --- Main ---
try { Start-Transcript -Path $LOG_PATH -Append -Force | Out-Null } catch { }

Write-Log "Detection v2.0 starting -- $env:COMPUTERNAME"

if (-not [Environment]::Is64BitProcess) {
    Write-Log "32-bit PowerShell detected" -Level ERROR
    Write-ComplianceResult -State "Failed" -Message "64-bit PowerShell required -- script ran in 32-bit process" -ExitCode 1
}

$sbEnabled = $false
try   { $sbEnabled = Confirm-SecureBootUEFI -ErrorAction Stop }
catch { $sbEnabled = $false }

Write-Log "Secure Boot enabled: $sbEnabled"

if (-not $sbEnabled) {
    Write-ComplianceResult -State "NotApplicable" -Message "Secure Boot disabled or BIOS mode -- out of scope" -ExitCode 0
}

$regState = Get-RegistryState
Write-Log "Layer 1 -- State: $($regState.State) | $($regState.Reason)"
Write-Log " UEFICA2023Status : $($regState.UEFICA2023Status)"
Write-Log " WindowsUEFICA2023Capable: $($regState.WindowsUEFICA2023Capable)"
Write-Log " AvailableUpdates : $($regState.AvailableUpdates)"

switch ($regState.State) {
    "PendingRemediation" { Write-ComplianceResult -State "PendingRemediation" -Message $regState.Reason -ExitCode 1 }
    "PendingRestart"     { Write-ComplianceResult -State "PendingRestart"     -Message $regState.Reason -ExitCode 1 }
    "Failed"             { Write-ComplianceResult -State "Failed"             -Message $regState.Reason -ExitCode 1 }
    "ManualReview"       { Write-ComplianceResult -State "ManualReview"       -Message $regState.Reason -ExitCode 1 }
}

Write-Log "Layer 2 starting -- UEFI DB thumbprint verification"
$uefResult = Test-UEFIDatabase

switch ($uefResult) {
    "Verified" {
        Write-ComplianceResult -State "Compliant" `
            -Message "Layer 1 + Layer 2 passed -- PCA2023 verified in UEFI DB" -ExitCode 0
    }
    "Missing" {
        Write-ComplianceResult -State "ManualReview" `
            -Message "Registry shows Updated but PCA2023 thumbprint absent from UEFI DB -- firmware investigation required" -ExitCode 1
    }
    "Unavailable" {
        Write-Log "Layer 2 unavailable -- falling back to Layer 1 result" -Level WARN
        Write-ComplianceResult -State "Compliant" `
            -Message "Layer 1 passed -- Layer 2 unavailable (UEFI read not supported), registry-based confirmation only" -ExitCode 0
    }
}