Intune
PKI
Active Directory
Autopilot


Hybrid Autopilot: Automating altSecurityIdentities

Published August 2025 - by Steve Prentice - tested on PowerShell 5.1

or Automating altSecurityIdentities updates in AD for Intune PKCS provided certificates during Hybrid Join Autopilot workflows


Contents Background Problem in hybrid Autopilot with early VPN Approach overview Prerequisites Create the gMSA Grant write access to altSecurityIdentities Create the triggered scheduled task The PowerShell processor script Troubleshooting tips Security summary

Background

Recently Microsoft introduced strong certificate binding within Active Directory Domain Services to address an elevation of privilege vulnerability that can occur when the Kerberos Key Distribution Center (KDC) is servicing a certificate-based authentication request. If you've previously postponed the enforcement of this, then from September 2025 Microsoft will start to fully enforce it by ignoring the postponement registry key. This update requires a non-critical certificate extension with OID 1.3.6.1.4.1.311.25.2 for default strong mappings to be embedded in the certificate. If a certificate is missing this extension you should re-deploy the certificates and the OID will be added, but if you can't re-deploy for whatever reason then you can strongly bind a certificate to an object using the object's altSecurityIdentities attribute.

KB5014754 - Certificate-based authentication changes on Windows domain controllers - strong certificate binding requirement.

Here's how you can work around issues when Autopilot certificates are issued too early.

Problem in hybrid join Autopilot with early VPN

In Microsoft Entra hybrid joined Autopilot workflows, a device actually starts life as a Microsoft Entra joined object and only becomes Hybrid AD joined later during the user OOBE. If you deploy a certificate for Always On VPN device-tunnel connections early so the machine can reach RRAS during device OOBE (and therefore get line of sight to a domain controller), there will not be enough information in Entra for Intune to stamp the strong mapping OID. The certificate is issued - but without the strong OID, and AD will not strongly map it by default.

Tip: For the initial early deployed certificate, you can delay the certificate issuance until after the device has its correct name in AD. Use the {{FullyQualifiedDomainName}} variable in your template to ensure issuance happens at the right place in the Autopilot process (although still too early to get the OID). Thanks to Michael Niehaus for the tip in his blog here.

Top tip: You can deploy a delayed second certificate later using an Intune filter - (device.deviceTrustType -eq "Hybrid Azure AD joined") - which will then include the OID because at that point the device is fully Hybrid joined and AD has had a chance to sync the required attributes to Entra.

However, if the early certificate must be usable, you can add a strong mapping manually by writing the correct X509 mapping string to altSecurityIdentities on the computer object as outlined in the above Microsoft KB article.

This situation is theoretical for most people, but if you've configured Windows to use certificates for logon, it may be more relevant to you.

Approach overview

  1. Listen for PKCS certificate issuance events on the Intune Certificate Connector server.
  2. When a target template is issued, trigger a PowerShell script.
  3. The script constructs the X509 mapping value from the issuer DN and the byte-reversed serial and writes it to the device's altSecurityIdentities in AD.

Principle of least privilege - use a gMSA limited to writing a single attribute on descendant computer objects in the specific OU where Autopilot devices land.

Prerequisites

Create the gMSA

A group Managed Service Account (gMSA) is a special type of Active Directory account that can run services or scheduled tasks without requiring stored passwords. In this case, we use a gMSA to securely run a scheduled task that updates altSecurityIdentities, reducing the risk of credential exposure and allowing tight scoping to only the connector servers.

# On a DC
Import-Module ActiveDirectory
New-ADServiceAccount -Name gMSA_altSecId `
  -DNSHostName contoso.com `
  -PrincipalsAllowedToRetrieveManagedPassword @(
    "CERT-SERVER-01$",
    "CERT-SERVER-02$"
  )
# On the connector server
Install-WindowsFeature RSAT-AD-PowerShell # If required
Import-Module ActiveDirectory
Install-ADServiceAccount -Identity gMSA_altSecId
Test-ADServiceAccount gMSA_altSecId

Grant write access to altSecurityIdentities

Grant a single extended right - WriteProperty on altSecurityIdentities - scoped to descendant computer objects under your target OU. This permission is required so the gMSA can update the altSecurityIdentities attribute on the computer accounts. Restricting the right to only descendant computer objects within the OU minimises the security risk. This change cannot be done through the AD GUI or with dsacls; you must use PowerShell to set it.

# Target OU
$ouDn = "OU=Computers,DC=contoso,DC=com"
$ouPath = "AD:$ouDn"

# gMSA account
$identity = New-Object System.Security.Principal.NTAccount("CONTOSO\\gMSA_altSecId$")

# Hardcoded schema GUIDs
$altSecIdGuid = [Guid]::Parse("00fbf30c-91fe-11d1-aebc-0000f80367c1")  # altSecurityIdentities
$computerClassGuid = [Guid]::Parse("bf967a86-0de6-11d0-a285-00aa003049e2")  # computer class

# Backup current ACL
$originalAcl = Get-Acl -Path $ouPath

# Define ACE
$rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
    $identity,
    [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty,
    [System.Security.AccessControl.AccessControlType]::Allow,
    $altSecIdGuid,
    [System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendents,
    $computerClassGuid
)

# Apply
$acl = $originalAcl
$acl.AddAccessRule($rule)
Set-Acl -Path $ouPath -AclObject $acl

# Optional verification
# dsacls $ouDn | findstr /i gMSA_altSecId

# Optional restore ACL (if you want to roll back the change)
# Set-Acl -Path $ouPath -AclObject $originalAcl

Create the triggered scheduled task

Create, export and delete a skeleton task, adjust the XML for Principal, Trigger filter, and Action arguments, then import it.

# Export existing task example
schtasks /query /TN "Event Viewer Tasks\Process PKCS events" /XML > "C:\Export-Process PKCS events.xml"

# Delete and recreate
schtasks /Delete /TN "Event Viewer Tasks\Process PKCS events" /F
schtasks /Create /XML "C:\Export-Process PKCS events.xml" /TN "Event Viewer Tasks\Process PKCS events"

What to replace in the XML

The provided task XML is a template. You'll need to adjust certain values so it works in your own environment. These changes ensure the scheduled task runs under the right account, triggers from the correct log source, and executes the intended script with matching parameters:

Example Task XML

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2025-01-01T00:00:00.0000000</Date> 
    <Author>CONTOSO\Admin</Author> 
    <Description>Process PKCS events</Description>
    <URI>\Event Viewer Tasks\Process PKCS events</URI>
  </RegistrationInfo>
  <Principals>
    <Principal id="Author">
      <UserId>S-1-5-21-xxxx-xxxx-xxxx-xxxx</UserId> 
      <LogonType>Password</LogonType>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    <IdleSettings>
  </Settings>
  <Triggers>
    <EventTrigger>
      <Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="Microsoft-Intune-CertificateConnectors/Admin"&gt;&lt;Select Path="Microsoft-Intune-CertificateConnectors/Admin"&gt;



      *[System[EventID=1000]] and *[EventData[Data[@Name='certificateTemplate']='AutopilotHybridJoinStagingCertificate']]



    &lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
      <ValueQueries>
        <Value name="CertificateAuthority">Event/EventData/Data[@Name='certificateAuthority']</Value>
        <Value name="Serial">Event/EventData/Data[@Name='serialNumber']</Value>
        <Value name="Subject">Event/EventData/Data[@Name='subjectName']</Value>
        <Value name="Template">Event/EventData/Data[@Name='certificateTemplate']</Value>
        <Value name="Thumbprint">Event/EventData/Data[@Name='thumbprint']</Value>
      </ValueQueries>
    </EventTrigger>
  </Triggers>
  <Actions Context="Author">
    <Exec>
      <Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
      <Arguments>-ExecutionPolicy Bypass -File "C:\Scripts\TriggeredPKCSRequestEvent.ps1" -ExecutionPolicy Bypass -File "C:\Scripts\TriggeredPKCSRequestEvent.ps1" -CertificateAuthority "$(CertificateAuthority)" -Serial "$(Serial)" -Subject "$(Subject)" -Template "$(Template)" -Thumbprint "$(Thumbprint)"</Arguments>
    </Exec>
  </Actions>
</Task>

The PowerShell processor script

This script consumes the event values, constructs the X509 mapping string, writes it to AD, emails a summary, and appends a CSV log. Create it in the location specified in the task's XML, for example C:\Scripts\TriggeredPKCSRequestEvent.ps1

param (
    [string]$CertificateAuthority,
    [string]$Serial,
    [string]$Subject,
    [string]$Template,
    [string]$Thumbprint
)

# --- LOGGING CONTROL ---
$logPath = "C:\Scripts\TriggeredPKCSRequestEvent.csv"
$transcriptPath = "C:\Scripts\Transcript_$(Get-Date -Format 'yyyy-MM-dd_HH-mm-ss').log"
try { Start-Transcript -Path $transcriptPath -Append -Force } catch {}

# --- DEBUG CONTROL ---
$SetADobject = $true # Set to $false to disable updating AD for testing
$SendEmail = $true   # Set to $false to disable email sending for testing
$smtpPort = 25
$smtpServer = "mail.contoso.com"
$emailFrom = "noreply@contoso.com"
$emailTo = "admin@contoso.com"

# Check for and import the ActiveDirectory module
if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
    throw "ActiveDirectory module not found. Please install RSAT: Active Directory tools." }
Import-Module ActiveDirectory -ErrorAction Stop

# Exit early if template is not AutopilotHybridJoinStagingCertificate
if ($Template -ne 'AutopilotHybridJoinStagingCertificate') {
    Write-Host "Template '$Template' does not match target. Exiting..."
    exit 0
}

# Clean up Subject by removing "CN=" prefix
$Subject = $Subject -replace '^CN=', ''
$computerName = (($Subject -split '\.')[0]).ToLower()

# Reverse Certificate Authority to build issuerDN
$parts = $CertificateAuthority -split '\\', 2
$caName = $parts[1]
$dcParts = ($parts[0] -split '\.', 2)[1] -split '\.'
[array]::Reverse($dcParts)
$issuerDN = "DC=$($dcParts -join ',DC='),CN=$caName"

# Reverse the serial number (remove non-hex, split, reverse, join)
$serialHex = ($Serial -replace '[^0-9A-Fa-f]', '').ToUpper()
$serialBytes = $serialHex -split '(.{2})' | Where-Object { $_ }
[array]::Reverse($serialBytes)
$reversedSerial = [string]($serialBytes -join '')

# Build altSecurityIdentities format
$altsec = [string]"X509:<I>$issuerDN<SR>$reversedSerial"

# Set AD object if enabled
if ($SetADobject) {
    try {
        if (Get-ADComputer -Identity $computerName -ErrorAction SilentlyContinue) {
            Set-ADComputer -Identity $computerName -Replace @{altSecurityIdentities = $altsec}
            Write-Host "altSecurityIdentities overwritten for $computerName"
        } else {
            Write-Warning "Computer '$computerName' not found in Active Directory"
        }
    }
    catch {
        Write-Warning "Failed to set altSecurityIdentities: $_"
    }
}

# Send email if enabled
if ($SendEmail) {
    try {
        # Build email body (HTML)
        $body = @"
<html>
<head>
<style>
  table { border-collapse: collapse; }
  td, th { border: 1px solid #ccc; padding: 6px 10px; }
  th { background-color: #f2f2f2; text-align: left; }
</style>
</head>
<body>
  <h3>PKCS Certificate Issued</h3>
  <table>
    <tr><th>Subject</th><td>$Subject</td></tr>
    <tr><th>Certificate Template</th><td>$Template</td></tr>
    <tr><th>Certificate Authority</th><td>$CertificateAuthority</td></tr>
    <tr><th>Issuer DN</th><td>$issuerDN</td></tr>
    <tr><th>altSecurityIdentities</th><td>$altsec</td></tr>
    <tr><th>Serial</th><td>$Serial</td></tr>
    <tr><th>Reversed Serial</th><td>$reversedSerial</td></tr>
    <tr><th>Thumbprint</th><td>$Thumbprint</td></tr>
    <tr><th>Sent From</th><td>$($env:COMPUTERNAME)</td></tr>
  </table>
</body>
</html>
"@
        Write-Host "Sending email to $emailTo..."
        $smtp = New-Object System.Net.Mail.SmtpClient($smtpServer, $smtpPort)
        $message = New-Object System.Net.Mail.MailMessage($emailFrom, $emailTo, "PKCS Certificate Issued: $Subject", $body)
        $message.IsBodyHtml = $true
        $smtp.Send($message)
    }
    catch {
        Write-Warning "Failed to send email: $_"
    }
    finally {
        if ($message) { $message.Dispose() }
        if ($smtp) { $smtp.Dispose() }
    }
}

# Output to CSV for logging
[pscustomobject]@{
    Timestamp  = Get-Date -Format o
    Computer   = $computerName
    Template   = $Template
    CA         = $CertificateAuthority
    Serial     = $Serial
    Thumbprint = $Thumbprint
} | Export-Csv -Path $logPath -NoTypeInformation -Append

Stop-Transcript

Troubleshooting

Even with the correct setup, a few common issues can crop up. These points will help you quickly identify and resolve problems without having to step through the entire configuration again:

Tips

Security summary

Allowing modification to altSecurityIdentities should be handled very carefully as it could introduce security vulnerabilities if not managed properly. Do not use a regular account for this. Instead, use a gMSA and ensure it is scoped to only the connector servers.

Thanks

Thanks to Michael Albert for his great blog on event triggered scheduled tasks.
Windows: Passing parameters to event triggered schedule tasks
Thanks to Michael Niehaus and Richard Hicks for their help and support while batting ideas around.