Remediate-SecureBootCA2023.ps1

<#PSScriptInfo
.VERSION 2.0
.GUID 3c9f7b2e-4d1a-4e8c-9c22-8f7d1a6b4c91
.AUTHOR Mert Efe Kanlikilic
.COMPANYNAME mertefekanlikilic.com
.COPYRIGHT (c) 2026 Mert Efe Kanlikilic
.TAGS SecureBoot UEFI Intune Remediation Compliance PCA2023
.LICENSEURI https://github.com/mertefekanlikilic
.PROJECTURI https://github.com/mertefekanlikilic
.DESCRIPTION Secure Boot CA 2023 certificate update remediation script for Intune compliance.
#>


<#
.SYNOPSIS
    Secure Boot CA 2023 certificate update remediation 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
        AvailableUpdates : 0x5944 -- triggers CA2023 update
        RemediationTimestamp : First remediation date (ISO 8601) -- written once
        RemediationAttemptCount : Total run count
        LastNotificationDay : Day threshold of last toast sent

    Toast notification timeline:
      Day 1 : Informational -- restart requested
      Day 3 : Reminder -- restart still pending
      Day 5 : Urgent -- security update delayed
      Day 7+ : IT Support -- escalation required
#>



$LOG_PATH          = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\SecureBoot-Remediation.log"
$EVENTLOG_SOURCE   = "SecureBootCA2023"
$EVENTLOG_LOG      = "Application"
$SECUREBOOT_KEY    = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot"
$SERVICING_KEY     = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing"
$AVAILABLE_UPDATES = 0x5944
$TASK_PATH         = "\Microsoft\Windows\PI\"
$TASK_NAME         = "Secure-Boot-Update"
$TOAST_APPID       = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe"

$NOTIFY_DAY1 = 0
$NOTIFY_DAY3 = 3
$NOTIFY_DAY5 = 5
$NOTIFY_DAY7 = 7

$TOAST_TEMPLATES = @{
    Day1 = @{
        Title   = "Security Update -- Restart Required"
        Message = "A Secure Boot certificate update has been applied to your device. Please restart your computer today to complete the update."
        Action1 = "Restart Now"
        Action2 = "Remind Me Later"
        Urgency = "default"
    }
    Day3 = @{
        Title   = "Security Update -- Restart Still Pending"
        Message = "Your Secure Boot certificate update is waiting for a restart. This is a security requirement. Please restart your computer as soon as possible."
        Action1 = "Restart Now"
        Action2 = "Remind Me Later"
        Urgency = "default"
    }
    Day5 = @{
        Title   = "Security Update -- Urgent Restart Needed"
        Message = "Your device security update has been pending for 5 days. Please restart your computer today. Continuing to delay may affect your device's security posture."
        Action1 = "Restart Now"
        Action2 = "I Understand"
        Urgency = "urgent"
    }
    Day7 = @{
        Title   = "Security Update -- IT Support Required"
        Message = "Your Secure Boot certificate update has not completed after multiple attempts. Please contact IT support or restart your device immediately. Reference: SecureBoot-CA2023."
        Action1 = "Restart Now"
        Action2 = "Contact IT Support"
        Urgency = "urgent"
    }
}

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 Get-RemediationMetadata {
    $meta = [PSCustomObject]@{
        FirstRunDate        = $null
        AttemptCount        = 0
        LastNotificationDay = -1
        DaysSinceFirst      = 0
    }
    try {
        $sb = Get-ItemProperty -Path $SECUREBOOT_KEY -ErrorAction SilentlyContinue
        if ($sb.RemediationTimestamp) {
            $meta.FirstRunDate    = [datetime]$sb.RemediationTimestamp
            $meta.DaysSinceFirst  = [math]::Floor(((Get-Date) - $meta.FirstRunDate).TotalDays)
        }
        if ($sb.RemediationAttemptCount) { $meta.AttemptCount        = [int]$sb.RemediationAttemptCount }
        if ($null -ne $sb.LastNotificationDay) { $meta.LastNotificationDay = [int]$sb.LastNotificationDay }
    }
    catch { }
    return $meta
}

function Set-RemediationMetadata {
    param([datetime]$FirstRunDate, [int]$AttemptCount, [int]$LastNotificationDay)
    try {
        if (-not (Test-Path $SECUREBOOT_KEY)) { New-Item -Path $SECUREBOOT_KEY -Force | Out-Null }
        $existing = (Get-ItemProperty -Path $SECUREBOOT_KEY -ErrorAction SilentlyContinue).RemediationTimestamp
        if (-not $existing) {
            Set-ItemProperty -Path $SECUREBOOT_KEY -Name "RemediationTimestamp" `
                -Value $FirstRunDate.ToString("o") -Type String -Force
        }
        Set-ItemProperty -Path $SECUREBOOT_KEY -Name "RemediationAttemptCount" -Value $AttemptCount        -Type DWord -Force
        Set-ItemProperty -Path $SECUREBOOT_KEY -Name "LastNotificationDay"     -Value $LastNotificationDay -Type DWord -Force
    }
    catch { Write-Log "Metadata write failed: $($_.Exception.Message)" -Level WARN }
}

function Set-AvailableUpdates {
    try {
        if (-not (Test-Path $SECUREBOOT_KEY)) { New-Item -Path $SECUREBOOT_KEY -Force | Out-Null }
        $current = (Get-ItemProperty -Path $SECUREBOOT_KEY -Name "AvailableUpdates" `
            -ErrorAction SilentlyContinue).AvailableUpdates
        if ($current -eq $AVAILABLE_UPDATES) {
            Write-Log "AvailableUpdates already 0x5944 -- skipping write"
            return $true
        }
        Set-ItemProperty -Path $SECUREBOOT_KEY -Name "AvailableUpdates" `
            -Value $AVAILABLE_UPDATES -Type DWord -Force
        $verify = (Get-ItemProperty -Path $SECUREBOOT_KEY -Name "AvailableUpdates").AvailableUpdates
        if ($verify -eq $AVAILABLE_UPDATES) {
            Write-Log "AvailableUpdates set to 0x5944"
            return $true
        }
        Write-Log "AvailableUpdates verification failed -- got: $verify" -Level ERROR
        return $false
    }
    catch {
        Write-Log "AvailableUpdates set failed: $($_.Exception.Message)" -Level ERROR
        return $false
    }
}

function Start-SecureBootTask {
    try {
        $task = Get-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction SilentlyContinue
        if ($null -eq $task) {
            Write-Log "Scheduled task not found: ${TASK_PATH}${TASK_NAME}" -Level WARN
            return $false
        }
        Start-ScheduledTask -TaskPath $TASK_PATH -TaskName $TASK_NAME -ErrorAction Stop
        Write-Log "Scheduled task triggered: ${TASK_PATH}${TASK_NAME}"
        return $true
    }
    catch {
        Write-Log "Scheduled task trigger failed: $($_.Exception.Message)" -Level WARN
        return $false
    }
}

function Get-NotificationTier {
    param([int]$DaysSince, [int]$LastNotifiedDay)
    if ($DaysSince -ge $NOTIFY_DAY7 -and $LastNotifiedDay -lt $NOTIFY_DAY7) { return "Day7" }
    elseif ($DaysSince -ge $NOTIFY_DAY5 -and $LastNotifiedDay -lt $NOTIFY_DAY5) { return "Day5" }
    elseif ($DaysSince -ge $NOTIFY_DAY3 -and $LastNotifiedDay -lt $NOTIFY_DAY3) { return "Day3" }
    elseif ($DaysSince -ge $NOTIFY_DAY1 -and $LastNotifiedDay -lt $NOTIFY_DAY1) { return "Day1" }
    return $null
}

function Send-ToastNotification {
    param([string]$Tier)
    $tmpl = $TOAST_TEMPLATES[$Tier]
    if (-not $tmpl) { Write-Log "Unknown toast tier: $Tier" -Level WARN; return }

    $scenarioAttr = if ($tmpl.Urgency -eq "urgent") { 'scenario="urgent"' } else { "" }
    $toastXml = @"
<toast $scenarioAttr>
    <visual>
        <binding template="ToastGeneric">
            <text>$($tmpl.Title)</text>
            <text>$($tmpl.Message)</text>
        </binding>
    </visual>
    <actions>
        <action content="$($tmpl.Action1)" arguments="restart-now" activationType="background"/>
        <action content="$($tmpl.Action2)" arguments="remind-later" activationType="background"/>
    </actions>
</toast>
"@


    try {
        $activeUser = (Get-WmiObject -Class Win32_ComputerSystem -ErrorAction Stop).UserName
        if (-not $activeUser) { Write-Log "No active user -- skipping toast" -Level WARN; return }

        $xmlOneLine  = $toastXml -replace "`r`n", " " -replace '"', '\"'
        $toastScript = @"
`$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
`$xml.LoadXml("$xmlOneLine")
`$toast = [Windows.UI.Notifications.ToastNotification]::new(`$xml)
`$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("$TOAST_APPID")
`$notifier.Show(`$toast)
"@

        $encoded   = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($toastScript))
        $userSid   = (New-Object System.Security.Principal.NTAccount($activeUser)).Translate(
                        [System.Security.Principal.SecurityIdentifier]).Value
        $tempTask  = "SecureBoot-Toast-$(Get-Random)"
        $action    = New-ScheduledTaskAction -Execute "powershell.exe" `
                        -Argument "-NonInteractive -WindowStyle Hidden -EncodedCommand $encoded"
        $principal = New-ScheduledTaskPrincipal -UserId $userSid -RunLevel Limited
        $settings  = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew

        Register-ScheduledTask -TaskName $tempTask -Action $action `
            -Principal $principal -Settings $settings -Force | Out-Null
        Start-ScheduledTask -TaskName $tempTask
        Start-Sleep -Seconds 5
        Unregister-ScheduledTask -TaskName $tempTask -Confirm:$false -ErrorAction SilentlyContinue

        Write-Log "Toast sent -- Tier: $Tier | User: $activeUser"
    }
    catch {
        Write-Log "Toast delivery failed: $($_.Exception.Message)" -Level WARN
    }
}

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

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

if (-not [Environment]::Is64BitProcess) {
    Write-Log "32-bit PowerShell -- 64-bit required" -Level ERROR
    Stop-Transcript -ErrorAction SilentlyContinue
    exit 1
}

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

Write-Log "Secure Boot enabled: $sbEnabled"
if (-not $sbEnabled) {
    Write-Log "Secure Boot disabled -- skipping remediation"
    Stop-Transcript -ErrorAction SilentlyContinue
    exit 0
}

try {
    $serv = Get-ItemProperty -Path $SERVICING_KEY -ErrorAction SilentlyContinue
    if ($serv.UEFICA2023Status -eq "Updated" -and [int]$serv.WindowsUEFICA2023Capable -eq 2) {
        Write-Log "Device already compliant -- skipping remediation"
        Stop-Transcript -ErrorAction SilentlyContinue
        exit 0
    }
}
catch { }

$meta = Get-RemediationMetadata
Write-Log "Metadata -- FirstRun: $($meta.FirstRunDate) | Attempts: $($meta.AttemptCount) | Days: $($meta.DaysSinceFirst) | LastNotified: $($meta.LastNotificationDay)"

$regOk = Set-AvailableUpdates
if (-not $regOk) {
    Write-Log "Registry write failed -- aborting remediation" -Level ERROR
    Write-EventEntry -Message "AvailableUpdates write failed -- $env:COMPUTERNAME" -EventId 1023 -EntryType "Error"
    Stop-Transcript -ErrorAction SilentlyContinue
    exit 1
}

$taskOk = Start-SecureBootTask
if (-not $taskOk) {
    Write-Log "Scheduled task not triggered -- Windows will run it automatically every 12 hours" -Level WARN
}

$newCount    = $meta.AttemptCount + 1
$firstRun    = if ($meta.FirstRunDate) { $meta.FirstRunDate } else { Get-Date }
$daysSince   = if ($meta.FirstRunDate) { $meta.DaysSinceFirst } else { 0 }

$tier = Get-NotificationTier -DaysSince $daysSince -LastNotifiedDay $meta.LastNotificationDay

$newLastNotifiedDay = $meta.LastNotificationDay
if ($tier) {
    Send-ToastNotification -Tier $tier
    $newLastNotifiedDay = switch ($tier) {
        "Day7" { $NOTIFY_DAY7 }
        "Day5" { $NOTIFY_DAY5 }
        "Day3" { $NOTIFY_DAY3 }
        "Day1" { $NOTIFY_DAY1 }
    }
    Write-Log "Toast tier: $tier -- LastNotificationDay updated: $newLastNotifiedDay"
}
else {
    Write-Log "No notification due this run (threshold not reached)"
}

Set-RemediationMetadata -FirstRunDate $firstRun -AttemptCount $newCount -LastNotificationDay $newLastNotifiedDay

$eventMsg = "Remediation complete -- Attempts: $newCount | Days: $daysSince | Tier: $tier | $env:COMPUTERNAME"
Write-EventEntry -Message $eventMsg -EventId 1020 -EntryType "Information"
Write-Log $eventMsg
Write-Log "Remediation done -- awaiting device restart"

Stop-Transcript -ErrorAction SilentlyContinue
exit 0