An Enrollment Status Page that sits on "Preparing your device for mobile management" for forty minutes tells the user nothing and tells you almost as little. The usual next step is collecting MDM diagnostics and reading several megabytes of log. There is a much faster path: the ESP writes its own progress into the registry as three JSON documents, one per phase, each containing every subcategory with its state and the exact status text the user saw. Read those and you know precisely where it stopped.
The ESP records its progress in three REG_SZ values under HKLM:\SOFTWARE\Microsoft\Provisioning\AutopilotSettings: DevicePreparationCategory.Status, DeviceSetupCategory.Status and AccountSetupCategory.Status. Each is a JSON blob with a categoryState plus one entry per subcategory - TPM attestation, Entra join, MDM enrollment, security policies, certificates, network, apps - each with its own subcategoryState and the literal subcategoryStatusText shown on screen, including counts like "Apps (3 of 3 installed)". A category stuck at inProgress with one subcategory also inProgress is your hang point, named exactly.
The problem: a progress bar with no diagnostics
The ESP is deliberately opaque to the end user - that is the point, it is a "please wait" screen. But when it hangs or fails, admins inherit the same opacity. The visible symptoms are all variations of:
- Stuck indefinitely on a phase heading like "Preparing your device for mobile management".
- A generic failure with a "Continue anyway" button, and no indication of what actually failed.
- Provisioning that completed, but the device is missing an app or policy and you need to know whether the ESP thought it succeeded.
- A device someone else provisioned, where you need the post-mortem days later.
In the last case especially, the logs may already have rolled. The registry record has not.
Why it happens: the ESP tracks phases and subcategories
Internally the ESP is a state machine over categories and subcategories. It needs to survive reboots mid-provisioning, so it persists that state rather than holding it in memory - which is precisely why it is still readable long afterwards. Each of the three phase values holds a JSON object with:
categoryState- the phase's overall state:notStarted,inProgress,succeeded, or a failure state.categoryStatusText- the phase-level text, for example "Completed".- One nested object per subcategory, each with
subcategoryStateand usuallysubcategoryStatusText- and that text is the literal string the user saw, counts included.
Those subcategory names are the useful part. "MdmEnrollmentSubcategory" hanging is a completely different investigation from "AppsSubcategory" hanging, and the phase heading on screen does not distinguish them.
AccountSetupCategory.Status reading notStarted across the board is usually not a fault. On many devices the user phase is skipped by configuration, or simply has not run for the account you are inspecting. Do not chase it as a failure unless you specifically expected the user phase to execute.How to verify: read the three JSON blobs
The values are raw JSON strings, so pipe them through ConvertFrom-Json to get something readable:
To find the hang point directly, look for anything still inProgress across all three phases:
The fix: identify the stuck subcategory, then act on it
Context: the registry read is diagnosis, not repair. What you do next depends entirely on which subcategory stopped, which is exactly why identifying it first matters. Mapping the common ones:
| Stuck subcategory | What it is waiting on | Where to look next |
|---|---|---|
TpmAttestationSubcategory | TPM attestation to Microsoft's attestation service | TPM health and firmware, plus outbound access to the attestation endpoints - a blocked proxy is a common cause |
AadjSubcategory | Microsoft Entra join to complete | Network and identity - and for hybrid profiles, the on-premises domain join path and its connector |
MdmEnrollmentSubcategory | Intune MDM enrollment | Enrollment restrictions, licensing, and device enrollment limits in Intune |
AppsSubcategory | Required apps to install - the status text carries the count | The blocking apps themselves; a single large or failing app holds the whole phase |
SecurityPoliciesSubcategory | Device configuration profiles to apply | The specific profiles targeted at the device, and any conflicts between them |
CertificatesSubcategory | Certificate profiles to deliver | SCEP/PKCS infrastructure and the NDES connector, if used |
To review or reduce what the ESP actually blocks on, in the Intune admin center:
- Sign in to intune.microsoft.com.
- Go to Devices › Enrollment › Enrollment Status Page.
- Select your profile and review Show app and profile configuration progress, the block/timeout settings, and critically the list of apps selected under Block device use until required apps are installed.
- Trim that app list to the genuine minimum - every app there is a chance to hang the phase.
Registry reference
| Value name (type) | Holds | Read it for |
|---|---|---|
DevicePreparationCategory.Status (REG_SZ) | JSON: TPM attestation, Entra join, MDM enrollment, ESP provider install | Failures before the device is even manageable |
DeviceSetupCategory.Status (REG_SZ) | JSON: security policies, certificates, network connections, apps, reboot coalescing | The most common hang phase - app and policy delivery, with counts in the status text |
AccountSetupCategory.Status (REG_SZ) | JSON: the same set again in user context, plus MFA preparation | User-phase problems; notStarted here is frequently normal |
Global.MDMEnrollmentStatus (REG_SZ) | Coarse enrollment state | A quick sanity check before parsing the JSON blobs |
Global.ShowContinueAnywayButton (REG_SZ) | true when the escape hatch is offered | Confirming whether a user could have bypassed a failing ESP - relevant when a device "provisioned" but is non-compliant |
UseRefactoredEsp (REG_SZ) | True on builds using the newer ESP implementation | Explaining why older guidance and value names may not match what you see |
Proof it worked: a full real capture, decoded
A genuine run of Get-AutopilotEspStatus.ps1 from this series against a real Autopilot-provisioned, Entra hybrid joined device. No redaction was needed - none of these fields carry device or tenant identifiers:
Read this as a post-mortem and notice how much is recoverable months later. Device Preparation and Device Setup both completed. The exact counts survived - "Security policies (1 of 1 applied)" and "Apps (3 of 3 installed)" - so you can prove not just that the phase succeeded but how much work it did. Certificates and network connections report "No setup needed", which is meaningfully different from "succeeded with nothing to do by accident": the ESP had nothing targeted at it.
Account Setup reads notStarted throughout. On this device that is expected, not a fault - the user phase did not run. This is exactly the case the gotcha above warns about, and it is worth seeing in a real capture so you do not raise it as an incident.
Finally, note SaveWhiteGloveSuccessResult under Device Setup. Its presence indicates the pre-provisioning (white glove) result path was exercised - a useful, non-obvious signal that a device came through a technician flow rather than straight user-driven OOBE.
References
- Microsoft Learn - Configure Windows Autopilot deployment profiles
- Microsoft Learn - Windows Autopilot registration overview
- Microsoft Learn - What's new in Windows Autopilot
Microsoft MVP community deep-dives
Verified and genuinely on-topic - each URL was fetched and confirmed before being cited here, not copied on trust:
| Author | Post | What it adds |
|---|---|---|
| Rudy Ooms (MVP, call4cloud.nl) | Step by Step: How Windows Retrieves the Autopilot Profile | Traces the full eight-step token-and-profile retrieval flow, and independently documents both the AutopilotPolicyCache registry key and the wmansvc on-disk cache this post relies on |
Script for this post is in Windows-Autopilot-Scripts.