← Back to articles Security

Secure Boot Certificates Expire June 2026 – Deploy Now via Intune | MSEndpoint

Secure Boot Certificates Expire June 2026 – Deploy Now via Intune | MSEndpoint

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.

Microsoft's Official Reference

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).

Stage 0
Secure Boot Disabled
UEFI Secure Boot is disabled in firmware or device uses Legacy BIOS. Certificate updates cannot apply.
exit 1
Stage 1
Deployment Not Triggered
AvailableUpdates not set and no deployment timestamp exists. Remediation will set AvailableUpdates = 0x5944.
exit 1
Stage 2
Configured – Awaiting WU
Deployment triggered. Windows Update has not yet processed the Secure-Boot-Update scheduled task.
exit 1
Stage 3
Update In Progress
AvailableUpdates bits are partially cleared — certificates are actively being enrolled into UEFI DB.
exit 1
Stage 4
CA2023 In DB – Reboot Needed
Windows UEFI CA 2023 is in the DB (Capable=1) but device hasn't rebooted into the new boot manager yet.
exit 1
Stage 5
COMPLIANT ✓
UEFICA2023Status = "Updated". Device is booting from the 2023-signed boot manager. Transition complete.
exit 0
WindowsUEFICA2023Capable = 2 Is Not Sufficient

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.

Figure 1 — Intune Remediation Architecture
Microsoft Intune Proactive Remediation Check Interval: Every 1h Detection Script Stages 0–5 compliance check exit 0 = OK · exit 1 = Remediate Detect-SecureBootCertUpdate.ps1 Remediation Script Sets AvailableUpdates = 0x5944 Triggers Secure-Boot-Update task Remediate-SecureBootCertUpdate.ps1 Windows Components Secure-Boot-Update Task Windows Update Service WinCsFlags.exe SecureBootUpdates folder UEFI Firmware Secure Boot DB KEK CA 2023 UEFI CA 2023 UEFICA2023Status IME Log File SecureBootCertificateUpdate.log Max 4MB + rotation Deploy Deploy exit 1 → Triggers Remediation reads ✓ UEFICA2023Status = "Updated" → exit 0
Figure 2 — Detection Decision Flow (Stages 0–5)
DETECTION START Secure Boot Enabled? NO Stage 0 · exit 1 YES UEFICA2023Status = "Updated"? YES Stage 5 COMPLIANT · exit 0 NO Deployment Triggered? NO Stage 1 · exit 1 YES CA2023Capable = 1 (In DB)? YES Stage 4 · exit 1 Reboot required NO AvailableUpdates changing? YES Stage 3 · exit 1 WU in progress NO Stage 2 · exit 1 Awaiting WU scan

Prerequisites & Device Requirements

Secure Boot EnabledUEFI Secure Boot must be ON in firmware. Legacy BIOS devices cannot proceed.
Windows 10/11 + July 2024 CUThe Secure-Boot-Update task requires the July 2024 or later cumulative update.
WinCsFlags.exe (preferred)Oct/Nov 2025+ CU includes WinCsFlags.exe — this is the preferred fallback method.
Recent OEM FirmwareFirmware must be less than 1 year old. The remediation script blocks on stale firmware to prevent BSOD.
Windows Update AccessDevices need WU connectivity (direct or WSUS) to receive the cumulative updates that process certificate bits.
BitLocker Recovery KeyHave recovery keys escrowed to Entra ID before deployment — cert changes may trigger recovery mode.

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 flags Fallback:ACTIVE so 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.
PowerShell · Detect-SecureBootCertificateUpdate.ps1 · v5.0
#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 = 0x5944 and triggers the Secure-Boot-Update task immediately. Writes a ManagedOptInDate timestamp.
  • 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 AvailableUpdates method if WinCS unavailable and payload files exist.
  • Blocked: Exits 1 if firmware too old, Secure Boot disabled, or no WinCS and no payload files.
PowerShell · Remediate-SecureBootCertificateUpdate.ps1 · v5.0
#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

Figure 3 — Intune Portal Configuration
intune.microsoft.com / devices / remediations / create Basics Scripts Scope tags Assignments Review + create Name Secure Boot Certificate Update 2026 Description Transition devices to Windows UEFI CA 2023 before June 2026 expiration Publisher MSEndpoint — Souhaiel Morhag Run schedule Every 1 hour ← recommended Run as System (64-bit) Run remediation Only if detection fails
1
Navigate to Proactive Remediations
In the Intune portal, go to Devices → Remediations → Create. Proactive Remediations require a Microsoft Intune Plan 1 license (included with M365 E3/E5 and Business Premium).
2
Configure Basics
Name: Secure Boot Certificate Update 2026. Publisher: your org. Description: note the June 2026 deadline and KB5025885 reference.
3
Upload Scripts
Upload 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".
4
Set Schedule
Set the detection run frequency to Every 1 hour. This ensures the detection tracks progress through stages efficiently and fires remediation promptly when needed.
5
Assign to Device Groups
Assign to all Windows 10/11 device groups. You can create a pilot group first (10–50 devices) and validate stage progression over a few days before expanding to all devices.
6
Monitor in Intune Reports
Under Reports → Endpoint Analytics → Proactive Remediations, review the Device status tab. Look for the output strings from the detection script: 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 StringStageAction RequiredStatus
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.

Summary: Your Action Plan

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.

Was this article helpful?

🎓 Ready to go deeper?

Practice real MD-102 exam questions, get AI feedback on your weak areas, and fast-track your Intune certification.

Start Free Practice → Book a Session
Souhaiel Morhag
Souhaiel Morhag
Microsoft Endpoint & Modern Workplace Engineer

Souhaiel Morhag is a Microsoft Intune and endpoint management specialist with hands-on experience deploying and securing enterprise environments across Microsoft 365. He founded MSEndpoint.com to share practical, real-world guides for IT admins navigating Microsoft technologies — and built the MSEndpoint Academy at app.msendpoint.com/academy, a dedicated learning platform for professionals preparing for the MD-102 (Microsoft 365 Endpoint Administrator) certification. Through in-depth articles and AI-powered practice exams, Souhaiel helps IT teams move faster and certify with confidence.

Related Articles

Popular on MSEndpoint