Preparing for the Secure Boot UEFI CA 2023 Update: Why Inventory is Your First Line of Defense

Preparing for the Windows UEFI CA 2023 Update: Why Inventory is Your First Line of Defense
Preparing for the Windows UEFI CA 2023 Update: Why Inventory is Your First Line of Defense

Introduction

The upcoming Windows Secure Boot UEFI CA 2023 certificate update is a critical security milestone for enterprises. This update strengthens Secure Boot by ensuring only trusted bootloaders and firmware components are allowed during the boot process, effectively mitigating rootkit and boot-level malware threats.

But here’s the challenge: before enforcing Secure Boot policies or deploying UEFI updates, organizations need visibility. Which devices are compliant? Which ones lack the update? Which systems don’t even support UEFI?

Inventory is the first and most essential step. Without accurate data on Secure Boot state, firmware type, and update readiness, enterprises risk non-compliance, operational disruptions, and security gaps.

This blog post shows you a PowerShell script that when deployed as a Remeditaion script from Intune, can help collect all the relevant details from managed devices and send them to Azure Log Analytics for centralized monitoring.

Note: Steps to set up an Azure Log Analytics Workspace are out of scope for this blog. Please refer to Microsoft documentation for workspace creation and configuration.

Why Inventory Matters for Secure Boot UEFI CA 2023 update?

  • Compliance Readiness: Identify devices that need remediation before enforcing policies.
  • Risk Mitigation: Prevent boot-level vulnerabilities by ensuring Secure Boot is enabled.
  • Operational Efficiency: Avoid surprises during certificate rollout by knowing your environment.

What The Script Does

The PowerShell script performs the below actions:

✅ Capturing general device details:

  • Device Name
  • Logged-in Username
  • User UPN
  • Serial Number
  • OS Version
  • Firmware Type (BIOS/UEFI)
  • Country (based on public IP)

✅ Checking Secure Boot compliance:

  • Is Secure Boot enabled?
  • Is the Windows UEFI CA 2023 update applied?
  • Registry-based update state

✅ Send the data to Azure Log Analytics using the HTTP Data Collector API.

Script Key Features

  • TLS 1.2 enforced for secure communication.
  • HMAC-SHA256 signature generation for authentication.
  • Payload size validation to avoid ingestion errors.
  • Compressed JSON for efficient data transfer.

The PowerShell Script

Here’s the full script: https://github.com/jbasuroy369/Secure-Boot-Compliance-Inventory-with-PowerShell-and-Azure-Log-Analytics/blob/main/SecureBoot-Inventory.ps1

#region initialize

# Enable TLS 1.2 support 
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Replace with your Log Analytics Workspace ID
$CustomerId = ""  

# Replace with your Primary Key
$SharedKey = ""

# Replace with your Custom Log name in Log Analytics
$DeviceLogName = ""

# Optional: keep blank; Azure Monitor will use ingestion time if not set
$TimeStampField = ""

#endregion initialize

# Function to create the authorization signature
Function New-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource) {
    $xHeaders = "x-ms-date:" + $date
    $stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource
    $bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
    $keyBytes = [Convert]::FromBase64String($sharedKey)
    $sha256 = New-Object System.Security.Cryptography.HMACSHA256
    $sha256.Key = $keyBytes
    $calculatedHash = $sha256.ComputeHash($bytesToHash)
    $encodedHash = [Convert]::ToBase64String($calculatedHash)
    $authorization = 'SharedKey {0}:{1}' -f $customerId, $encodedHash
    return $authorization
}

# Function to create and post the request
Function Send-LogAnalyticsData($customerId, $sharedKey, $body, $logType) {
    $method = "POST"
    $contentType = "application/json"
    $resource = "/api/logs"
    $rfc1123date = [DateTime]::UtcNow.ToString("r")
    $contentLength = $body.Length
    $signature = New-Signature `
        -customerId $customerId `
        -sharedKey $sharedKey `
        -date $rfc1123date `
        -contentLength $contentLength `
        -method $method `
        -contentType $contentType `
        -resource $resource
    $uri = "https://${customerId}.ods.opinsights.azure.com${resource}?api-version=2016-04-01"
    # Validate that payload data does not exceed limits
    if ($body.Length -gt (31.9 *1024*1024)) {
        throw("Upload payload is too big and exceeds the 32Mb limit. Current payload size: " + ($body.Length/1024/1024).ToString("#.#") + "Mb")
    }
    $payloadsize = ("Upload payload size is " + ($body.Length/1024).ToString("#.#") + "Kb ")
    $headers = @{
        "Authorization"        = $signature
        "Log-Type"             = $logType
        "x-ms-date"            = $rfc1123date
        "time-generated-field" = $TimeStampField
    }
    $response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
    $statusmessage = "$($response.StatusCode) : $($payloadsize)"
    return $statusmessage 
}

# Capture device details
$deviceName = $env:COMPUTERNAME
$loginName = (Get-CimInstance -ClassName Win32_ComputerSystem).UserName
if (-not $loginName) { $loginName = $env:USERNAME }

# Extract UPN using regex
$dsreg = dsregcmd /status
$upn = ($dsreg | Select-String "Executing Account Name").ToString().Split(",")[1].Trim()

# Get Serial Number and OS Version
$serialNumber = (Get-CimInstance Win32_BIOS).SerialNumber
$osVersion = (Get-CimInstance Win32_OperatingSystem).Version
$firmwareType = $env:firmware_type

# Get Country based on public IP
try {
    $publicIP = (Invoke-RestMethod -Uri "https://api.ipify.org?format=json").ip
    $geoInfo  = Invoke-RestMethod -Uri "https://ipinfo.io/$publicIP/json"
    $country  = $geoInfo.country
} catch {
    $country = "Unable to retrieve country"
}

# Secure Boot checks
try {
    $secureBootStatus = Confirm-SecureBootUEFI
} catch {
    $secureBootStatus = "Secure Boot status could not be determined. System may not support UEFI."
}

try {
    $secureBootUpdateStatus = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI db).bytes) -match 'Windows UEFI CA 2023'
} catch {
    $secureBootUpdateStatus = "Secure Boot Update status could not be determined."
}

$secureBootPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot"
$servicingPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing"
$availableKey = "AvailableUpdates"
$statusKey = "UEFICA2023Status"
$secureBootUpdateState = "Unknown"

try {
    $availableUpdates = (Get-ItemProperty -Path $secureBootPath -Name $availableKey -ErrorAction Stop).$availableKey
    if ($availableUpdates -eq 0) {
        $secureBootUpdateEnabled = "False"
    } else {
        $secureBootUpdateEnabled = "True"
        try {
            $secureBootUpdateState = (Get-ItemProperty -Path $servicingPath -Name $statusKey -ErrorAction Stop).$statusKey
        } catch {
            $secureBootUpdateState = "UEFICA2023Status key not found."
        }
    }
} catch {
    $secureBootUpdateEnabled = "Registry key not found or inaccessible."
}

# Create inventory payload
$Inventory = [pscustomobject]@{
    DeviceName                = $deviceName
    Username                  = $loginName
    UserUPN                   = $upn
    SerialNumber              = $serialNumber
    OSVersion                 = $osVersion
    FirmwareType              = $firmwareType
    Country                   = $country
    SecureBootState           = $secureBootStatus
    SecureBootUpdateStatus    = $secureBootUpdateStatus
    SecureBootUpdateEnabled   = $secureBootUpdateEnabled
    SecureBootUpdateState     = $secureBootUpdateState
}

# Convert to JSON and send to Log Analytics
$DeviceJson = $Inventory | ConvertTo-Json -Depth 5 -Compress
$ResponseDeviceInventory = Send-LogAnalyticsData $CustomerId $SharedKey ([System.Text.Encoding]::UTF8.GetBytes($DeviceJson)) $DeviceLogName

# Output results
$date = Get-Date -Format "dd-MM HH:mm"
if ($ResponseDeviceInventory -match "200 :") {
    Write-Host "InventoryDate: $date - DeviceInventory: SUCCESS - $ResponseDeviceInventory"
} else {
    Write-Host "InventoryDate: $date - DeviceInventory: FAILED - $ResponseDeviceInventory"
}

Write-Host ("DeviceName: {0}`nUsername: {1}`nUserUPN: {2}`nSerialNumber: {3}`nOSVersion: {4}`nFirmwareType: {5}`nCountry: {6}`nSecureBootState: {7}`nSecureBootUpdateStatus: {8}`nSecureBootUpdateEnabled: {9}`nSecureBootUpdateState: {10}" -f `
    $deviceName, $loginName, $upn, $serialNumber, $osVersion, $firmwareType, $country, $secureBootStatus, $secureBootUpdateStatus, $secureBootUpdateEnabled, $secureBootUpdateState)

Check Sample Output

DeviceName: LAPTOP123
Username: CONTOSO\jdoe
UserUPN: jdoe@contoso.com
SerialNumber: ABC123XYZ
OSVersion: 10.0.22631
FirmwareType: UEFI
Country: IN
SecureBootState: True/False
SecureBootUpdateStatus: True/False
SecureBootUpdateEnabled: True/False
SecureBootUpdateState: NotStarted, InProgress, Updated, Unknown
The image shows a Log Analytics query result for the custom table SecureBootDetails_CL in Azure Monitor. The image confirms that the script successfully ingested data into Log Analytics and the custom fields are visible for compliance reporting.

Query in Log Analytics

You can use KQL to analyze data as collected. Example

DeviceDetails_CL
| summarize count() by SecureBootState, SecureBootUpdateStatus, Country

Benefits

  • Compliance Reporting: Identify non-compliant devices before rollout.
  • Security Posture: Ensure Secure Boot and UEFI updates are enforced.
  • Automation: No manual checks—data flows directly to Azure.

Next Steps

  • Build Power BI dashboards on top of Log Analytics.
  • Automate alerts for non-compliant devices using Azure Monitor.

Reference

4 Comments

  1. did you check the scrip? log analytics doesn’t offer this kind of of connection to log analytics agent anymore since over a year…

Comments are closed.