Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Trigger Intune Automatic Enrollment on a Windows Device with PowerShell

For the Entra-joined PC that never showed up in Intune. Check it's ready, kick off enrollment, and see why it failed if it does.

AT A GLANCEStart-IntuneEnrollment.ps1
What it does
Checks the device's Entra join state and existing MDM enrollment, runs Windows' built-in automatic enrollment if it's needed, waits for the result, and returns it along with the matching events from the MDM log.
Requires
  • Windows PowerShell 5.1 or PowerShell 7
  • A device that's Microsoft Entra joined or hybrid joined
  • No modules
Permissions
Local administrator on the device. The user signing in needs an Intune license and has to be inside the MDM user scope.
Runs on
Windows 10 1903+ and Windows 11
Tested
Parse-checked and dry-run with mocked dsregcmd, registry and event log calls in PowerShell 7.4

Let's clear something up first, because the original version of this post got it wrong. There's no Graph call or cmdlet that reaches out and "adds" a Windows PC to Intune. Enrollment always happens on the device, which asks Intune to take it on. The old script used cmdlets that never existed, and I'd rather replace it than leave it up.

What you can do from PowerShell is poke the device into enrolling itself. Windows ships with the same mechanism the "Enable automatic MDM enrollment using default Azure AD credentials" Group Policy uses: deviceenroller.exe. On a device that's Entra joined or hybrid joined, it enrolls into Intune with the signed-in user's token. No prompts, no Settings app.

This script wraps that in the checks you'd otherwise do by hand. Is the device actually joined? Is it already enrolled? Did the enrollment work, and if not, what error did Windows log? It's what I reach for when a hybrid-joined PC has been sitting in Entra for a week and never showed up in Intune.

Start-IntuneEnrollment.ps1Download
<#
.SYNOPSIS
    Checks whether a Windows device is ready for Intune automatic enrollment, triggers it
    if it isn't enrolled yet, and tells you what happened.
.DESCRIPTION
    Reads the join state from dsregcmd, looks for an existing MDM enrollment in the registry,
    and if the device is Entra joined (or hybrid joined) but not enrolled, runs the same
    built-in command the "Enable automatic MDM enrollment" Group Policy uses. Then it waits
    for the result and pulls the matching events from the MDM diagnostics log. Supports -WhatIf.
    Run it elevated, on the device itself.
.PARAMETER UseDeviceCredential
    Enroll with the device's credential instead of the signed-in user's. Needed when nobody
    is signed in (for example, co-management scenarios).
.PARAMETER WaitSeconds
    How long to wait for the enrollment to show up before giving up. Default: 120.
.EXAMPLE
    .\Start-IntuneEnrollment.ps1 -WhatIf
.EXAMPLE
    .\Start-IntuneEnrollment.ps1 -UseDeviceCredential -WaitSeconds 300
#>
#Requires -RunAsAdministrator
[CmdletBinding(SupportsShouldProcess)]
param(
    [switch]$UseDeviceCredential,
    [ValidateRange(0, 1800)]
    [int]$WaitSeconds = 120
)

function Get-JoinState {
    $state = @{}
    foreach ($line in (& dsregcmd.exe /status)) {
        if ($line -match '^\s*(\w+)\s*:\s*(.+?)\s*$') { $state[$matches[1]] = $matches[2] }
    }
    $state
}

function Get-MdmEnrollment {
    # An Intune enrollment lives under this key with ProviderID "MS DM Server".
    Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Enrollments' -ErrorAction SilentlyContinue |
        Get-ItemProperty -ErrorAction SilentlyContinue |
        Where-Object { $_.ProviderID -eq 'MS DM Server' } |
        Select-Object -First 1
}

$join     = Get-JoinState
$existing = Get-MdmEnrollment
$status = [pscustomobject]@{
    ComputerName    = $env:COMPUTERNAME
    EntraJoined     = $join['AzureAdJoined'] -eq 'YES'
    DomainJoined    = $join['DomainJoined'] -eq 'YES'
    Tenant          = $join['TenantName']
    EnrolledBefore  = [bool]$existing
    EnrolledAfter   = [bool]$existing
    EnrolledUpn     = $existing.UPN
    Result          = $null
    RecentEvents    = $null
}

if ($existing) {
    $status.Result = 'Already enrolled'
    return $status
}
if (-not $status.EntraJoined) {
    $status.Result = if ($status.DomainJoined) { 'Not ready: domain joined but not hybrid joined yet (check Entra Connect device sync)' } else { 'Not ready: device is not Entra joined' }
    return $status
}

$argument = if ($UseDeviceCredential) { '/AutoEnrollMDMUsingAADDeviceCredential' } else { '/AutoEnrollMDM' }
if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "deviceenroller.exe /c $argument")) {
    $status.Result = 'WhatIf'
    return $status
}

$started = Get-Date
& "$env:windir\System32\deviceenroller.exe" /c $argument
Write-Verbose "Enrollment requested with $argument. Waiting up to $WaitSeconds seconds."

$deadline = $started.AddSeconds($WaitSeconds)
do {
    Start-Sleep -Seconds 10
    $existing = Get-MdmEnrollment
} until ($existing -or (Get-Date) -ge $deadline)

# Event 75 = auto-enrollment succeeded, 76 = failed (the message carries the error code).
$status.RecentEvents = @(Get-WinEvent -FilterHashtable @{ LogName = 'Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin'; Id = 75, 76; StartTime = $started } -ErrorAction SilentlyContinue |
    ForEach-Object { '{0:HH:mm:ss} [{1}] {2}' -f $_.TimeCreated, $_.Id, $_.Message })

$status.EnrolledAfter = [bool]$existing
$status.EnrolledUpn   = $existing.UPN
$status.Result = if ($existing) { 'Enrolled' } elseif ($status.RecentEvents -match '\[76\]') { 'Failed: see RecentEvents' } else { 'No result yet: check the device in Intune in a few minutes' }
$status

Parameters

ParameterTypeDefaultWhat it's for
-UseDeviceCredentialswitch—Enroll with the device's own Entra credential instead of the signed-in user's. Use it when nobody's signed in, or for co-management.
-WaitSecondsint120How long to wait for the enrollment to appear before giving up. Anywhere from 0 to 1800.
-WhatIfswitch—Run the readiness checks and show what it would do, without enrolling.

Run it

Just check. Nothing gets enrolled.

.\Start-IntuneEnrollment.ps1 -WhatIf

The normal fix, with the user signed in.

.\Start-IntuneEnrollment.ps1

No user around, so enroll with the device credential and give it longer.

.\Start-IntuneEnrollment.ps1 -UseDeviceCredential -WaitSeconds 300

Just the evidence, for the ticket.

.\Start-IntuneEnrollment.ps1 | Select-Object -ExpandProperty RecentEvents

What you'll see

Example outputvalues are illustrative
ComputerName   : PC-0142
EntraJoined    : True
DomainJoined   : True
Tenant         : Contoso
EnrolledBefore : False
EnrolledAfter  : False
EnrolledUpn    :
Result         : Failed: see RecentEvents
RecentEvents   : {14:15:02 [76] Auto MDM Enroll: Failed (Unknown Win32 Error code: 0x8018002b)}

How it works

  1. Read the join state. dsregcmd /status is parsed into key/value pairs, so the script knows whether the device is Entra joined, domain joined, and which tenant it belongs to.
  2. Look for an existing enrollment. Intune enrollments live under HKLM:\SOFTWARE\Microsoft\Enrollments with a ProviderID of MS DM Server. If one's there, you're done, and it tells you which UPN enrolled it.
  3. Stop early if it can't work. Not joined? It explains why (including the common "domain joined but hybrid join hasn't finished" case) instead of running something that's going to fail.
  4. Trigger enrollment. deviceenroller.exe /c /AutoEnrollMDM, or /AutoEnrollMDMUsingAADDeviceCredential with the switch. It's the same built-in command the Group Policy schedules.
  5. Wait, then collect evidence. It polls the registry for up to -WaitSeconds, then pulls events 75 (success) and 76 (failure) from the DeviceManagement-Enterprise-Diagnostics-Provider/Admin log. The failure event includes the error code, which is the thing you'll actually want to search for.

Take it further

  • Use the Group Policy for the whole fleet. For hybrid-joined devices, the "Enable automatic MDM enrollment using default Azure AD credentials" setting does this on a schedule. The script is for the stragglers.
  • Run it from your RMM or ConfigMgr. Its output is a single object, so it's easy to collect from many machines and filter for the ones that failed. Just remember the signed-in-user rule above.
  • Brand-new devices? Skip all this and register them for Windows Autopilot instead. The next post in this series covers Get-WindowsAutopilotInfo.

Things that'll trip you up

  • Hybrid join has to finish first. A domain-joined PC isn't hybrid joined until Entra Connect has synced the computer object and the device has registered. If dsregcmd says AzureAdJoined is NO, enrollment can't work yet, and the script says so instead of trying.
  • The MDM user scope decides a lot. In the Entra admin center, under Mobility (MDM and WIP), Microsoft Intune's MDM user scope has to include the user, either All or a group they're in. If it's set to None, every enrollment fails, and 0x8018002b is the error you'll usually see.
  • Licensing and UPNs. The user needs an Intune license, and their UPN has to be on a verified domain. A UPN still ending in .local will fail every time.
  • Device credential enrollment has limits. Devices enrolled with -UseDeviceCredential have no primary user, so Company Portal and user-targeted apps behave differently. It's meant for co-management and shared devices, not everyday laptops.
  • Somebody has to be signed in. The default mode enrolls with the signed-in user's Entra token, so it needs a real user session on the device, ideally the person who'll own it. With nobody signed in it fails. That's the case -UseDeviceCredential is for.