Advanced IME and ESP Troubleshooting with PowerShell Scripts

by | Nov 7, 2024 | Blog

When the Microsoft Intune Management Extension (IME) gets stuck in the Enrollment Status Page (ESP), pinpointing the exact cause can feel like looking for a needle in a haystack. But with the right tools, like PowerShell, and this Advanced IME and ESP Troubleshooting guide, you can quickly get to the root of the issue.

This post will walk you through the IME’s ESP detection flow step-by-step. We’ll show you how each phase works and provide scripts to help you determine the ESP phase yourself.

Introduction and a small recap

Our last blog explored how devices can get stuck on the Enrollment Status Page (ESP), particularly when the Intune Management Extension (IME) can’t progress beyond the Account Setup phase. The IME relies on specific registry keys and status checks to determine each phase of the ESP flow. By taking a look at the Intune Management Extension logs, you could see it for yourself, but here’s a quick recap of how it works:

  1. Check FirstSync: IME first verifies if the device has completed initial syncing with Intune by checking the FirstSyncSID registry key. If the key is missing or IsSyncDone is not set to 1, IME halts at Account Setup, assuming the sync isn’t complete.

  2. Verify Sidecar Policy Provider Installation: After confirming sync, IME checks the InstallationState of the Sidecar Policy Provider. If not set to “Completed,” it indicates that essential provisioning policies aren’t fully deployed yet.

  3. Confirm Provisioning Completion: IME then verifies the HasProvisioningCompleted status to ensure that the device provisioning process is done. A true value here means the device is ready to continue beyond provisioning.

  4. Track Sidecar Policy Creation: Once provisioning completes, IME verifies that tracking policies are in place by checking the TrackingPoliciesCreated flag. If missing, it pauses until these policies are properly applied.

  5. Device and Account Setup Checks: Finally, IME checks the installation state of all required apps in both the device and user phases. Any app that’s not fully installed, or in progress, will delay IME from marking the ESP as complete.

Diving into the GetESPPhase step by step

Now, let’s dive into each ESP phase, adding PowerShell scripts that let you check these settings directly and troubleshoot where the Microsoft Intune Management Extension (IME) might be encountering issues.

Step 1: CheckESPPhase and the FirstSync Key

After opening the Intune Management Extension log files with the CMTrace tool, we will notice that the FirstSyncSID key indicates whether a device’s initial sync was completed successfully. If it’s present, ESP should set the NotInEsp status.

Inside the Appworkload.log we will spot if the the fistsync is being detected or not.

If that key below (SID) doesn’t exist and with it, the IsSyncDone is not set to 1, the IME will stay in the Account Setup Phase.

Inside the firstsync registry key we will spot if the syncisdone. If the first sync of the user is completed this registr key will be set to 1

You can check it yourself by running this PowerShell Script:

# Define the base registry path
$basePath = "HKLM:\SOFTWARE\Microsoft\Enrollments"
# Get all enrollment GUIDs under the base path
$enrollments = Get-ChildItem -Path $basePath
foreach ($enrollment in $enrollments) {
    # Check if the ProviderID is set to "MS DM Server"
    $providerID = (Get-ItemProperty -Path $enrollment.PSPath -Name "ProviderID" -ErrorAction SilentlyContinue).ProviderID
    if ($providerID -eq "MS DM Server") {
        # Check if there is a "FirstSync" key under the current enrollment for device-level sync status
        $firstSyncPathDevice = Join-Path -Path $enrollment.PSPath -ChildPath "FirstSync"
        if (Test-Path -Path $firstSyncPathDevice) {
            # Check device-level "IsSyncDone" status
            try {
                $isSyncDoneDevice = (Get-ItemProperty -Path $firstSyncPathDevice -Name "IsSyncDone" -ErrorAction Stop).IsSyncDone
                if ($isSyncDoneDevice -eq 1) {
                    Write-Host "Device-level IsSyncDone: Completed" -ForegroundColor Green
                } elseif ($isSyncDoneDevice -eq 0) {
                    Write-Host "Device-level IsSyncDone: Not Completed" -ForegroundColor Red
                } else {
                    Write-Host "Device-level IsSyncDone: Unexpected Value ($isSyncDoneDevice)" -ForegroundColor Yellow
                }
            } catch {
                Write-Host "Device-level IsSyncDone: Missing" -ForegroundColor Red
            }
        }
        # Check if there is a "FirstSync" key under the current enrollment for user-level sync status

        if (Test-Path -Path $firstSyncPathDevice) {
            # Get all SIDs under the "FirstSync" path
            $sids = Get-ChildItem -Path $firstSyncPathDevice -ErrorAction SilentlyContinue
            if ($sids.Count -eq 0) {
                # If FirstSync is found but no SID subkeys exist, output an error message
                Write-Host "User-level IsSyncDone: No SIDs Found" -ForegroundColor Red
            } else {
                # Loop through each SID if present
                foreach ($sid in $sids) {
                    try {
                        # Attempt to retrieve the "IsSyncDone" value for each SID
                        $isSyncDoneValue = (Get-ItemProperty -Path $sid.PSPath -Name "IsSyncDone" -ErrorAction Stop).IsSyncDone
                        # Output based on the IsSyncDone value
                        if ($isSyncDoneValue -ne $null) {
                            if ($isSyncDoneValue -eq 1) {
                                Write-Host "User-level IsSyncDone: Completed" -ForegroundColor Green
                            } elseif ($isSyncDoneValue -eq 0) {
                                Write-Host "User-level IsSyncDone: Not Completed" -ForegroundColor Red
                            } else {
                                Write-Host "User-level IsSyncDone: Unexpected Value ($isSyncDoneValue)" -ForegroundColor Yellow
                            }
                        }
                    } catch {
                        # Handle cases where IsSyncDone does not exist, but only if the SID exists
                        Write-Host "User-level IsSyncDone: Missing" -ForegroundColor Red

                    }
                }
            }
        }
    }
}

So, for example if the FirstSync key is created but the user-level Issyncdone is missing, this script will show you the outcome:

user-level syncisdone is missing

Step 2: Verify InstallationState of the Sidecar PolicyProvider

After trying to find the IsSyncDone for the logged in use, the IME now starts checking the SideCar InstallationState. .

The Intune Management Extension (IME) will start checking the status of the ESP sidecar by looking at the installationstate

This code will check the InstallationState and if it is set to complete, not installed, or in progress

It will do so by checking the policyproviderssidcar key. Inside that registry key there should be a key named installationstate

The PowerShell Script Example:

function Get-PolicyProviderInstallationState {
    param (
        [string]$InstanceID = "Sidecar"
    )
    Write-Output "[Win32App] Checking InstallationState for PolicyProvider with InstanceID: $InstanceID"
    try {
        # Query the WMI class for the specific PolicyProvider installation state
        $providerQuery = Get-WmiObject -Namespace "root\cimv2\mdm\dmmap" -Query "SELECT InstanceID, InstallationState FROM MDM_EnrollmentStatusTracking_PolicyProviders02_01"
        foreach ($provider in $providerQuery) {
            if ($provider.InstanceID -eq $InstanceID) {
                Write-Output "[Win32App] Found InstanceID: $($provider.InstanceID) with InstallationState: $($provider.InstallationState)"
                # Map the InstallationState integer value to a readable status (if needed)
                switch ($provider.InstallationState) {
                    0 { $state = "Unknown" }
                    1 { $state = "NotInstalled" }
                    2 { $state = "NotRequired" }
                    3 { $state = "Completed" }
                    default { $state = "Unknown" }
                }
                Write-Output "[Win32App] InstallationState for PolicyProvider '$InstanceID' is $state"
                return $state
            }
        }
        Write-Output "[Win32App] PolicyProvider with InstanceID '$InstanceID' not found."
        return "NotFound"
    }
    catch {
        Write-Output "[Win32App] Failed to check InstallationState with exception: $_"
        return "Error"
    }
}

Step 3: Verify HasProvisioningCompleted

After checking the Sidecar Intune Agent, the IME verifies whether the device provisioning is marked complete.

the IME verifies whether the device provisioning is marked complete.

This code above, checks if the hasprovisioningcompleted has been set.

We can spot the same in the appworkload.log (Intune log)

The Appworkload Intune Log would mention if the hasprovisioningcompletedwmi value as false or true

Use this to check its status:

function Get-HasProvisioningCompleted {
    Write-Output "[Win32App] Checking HasProvisioningCompleted status for device"
    try {
        # Query the WMI class for HasProvisioningCompleted status
        $provisioningQuery = Get-WmiObject -Namespace "root\cimv2\mdm\dmmap" -Query "SELECT HasProvisioningCompleted FROM MDM_EnrollmentStatusTracking_Setup01"      
        foreach ($result in $provisioningQuery) {
            if ($result.HasProvisioningCompleted -ne $null) {
                Write-Output "[Win32App] Found HasProvisioningCompleted: $($result.HasProvisioningCompleted)"
                return [bool]$result.HasProvisioningCompleted
            }
        } 
        Write-Output "[Win32App] HasProvisioningCompleted property not found."
        return $false
    }
    catch {
        Write-Output "[Win32App] Failed to check HasProvisioningCompleted with exception: $_"
        return $false
    }
}

Step 4: Check Sidecar Tracking Policies Created

Once provisioning completes, IME checks if tracking policies are set.

after the provisioning is completed, the IME will start checking if the tracking policies are set.

Run this script to check Sidecar tracking policies:

function Get-SidecarTrackingPoliciesCreated {
    param (
        [string]$UserSID = $null
    )
    Write-Output "[Win32App] Checking Sidecar Tracking Policies Created status"
    $context = New-Object System.Management.ManagementNamedValueCollection
    if ($UserSID) {
        $context.Add("PolicyPlatformContext_PrincipalContext_Type", "PolicyPlatform_UserContext")
        $context.Add("PolicyPlatformContext_PrincipalContext_Id", $UserSID)
    }
    # Query WMI to get tracking policies created status
    try {
        $wmiQuery = Get-WmiObject -Namespace "root\cimv2\mdm\dmmap" -Query "SELECT TrackingPoliciesCreated FROM MDM_EnrollmentStatusTracking_PolicyProviders03_01"
        return $wmiQuery.TrackingPoliciesCreated
    } catch {
        Write-Output "[Win32App] Failed to retrieve Sidecar Tracking Policies Created status"
        return $false
    }
}

Step 5: Device and Account Setup

IME continues with checking all tracked apps in the device and user/account phases to confirm they’re complete. It does so by kicking off the CheckDeviceAndAccountSetupStateWithWmi IME function.

the Intune Management Extension will kick off the CheckDeviceAndAccountSetupStateWithWmi IME function to check if all the requried tracked apps are installed and completed

We can do the same for the device-level checks, using this script:

# Step 3: Check app tracking status for the device phase (via WMI)

function Get-DeviceAppTrackingState {
    $deviceApps = Get-CimInstance -Namespace "root\cimv2\mdm\dmmap" -ClassName "MDM_EnrollmentStatusTracking_Tracking03_02" -ErrorAction SilentlyContinue
    $rebootRequired = $false
    $incompleteApps = $false
    foreach ($app in $deviceApps) {
        $instanceID = $app.InstanceID
        $installationState = $app.InstallationState
        $status = ""
        # Determine app status based on InstallationState values
        if ($installationState -eq 1) {
            $status = "Not Installed"
            $incompleteApps = $true
        } elseif ($installationState -eq 2) {
            $status = "In Progress"
            $incompleteApps = $true
        } elseif ($installationState -eq 3) {
            $status = "Reboot Required"
            $rebootRequired = $true
        } else {
            $status = "Installed"
        }
        # Output each app's status
        Write-Host "App InstanceID: $instanceID - Installation State: $installationState - Status: $status"
    }
    # Summary check for reboot or incomplete apps
    if ($rebootRequired -or $incompleteApps) {
        if ($rebootRequired) {
            Write-Host "`nA reboot is required - Device setup is incomplete."
        }
        if ($incompleteApps) {
            Write-Host "`nSome apps are not installed or in progress - Device setup is incomplete."
        }
        return $true
    } else {
        Write-Host "All apps are installed and no reboot is required - Device setup is complete."
        return $false
    }
}

This script will check the installation state of the tracked applications

the script above would check if all required apps are installed and their corrosponding installation state

Wrapping Up

These PowerShell scripts offer a direct way to monitor and troubleshoot the ESP phases. By running these checks, you can pinpoint where IME might be getting stuck in the Account Setup phase, helping you resolve ESP issues without extensive guesswork

Here’s a thought: you can even combine these scripts to build your own requirement rule for Win32 apps. Doing so allows you to make them applicable only if the device is in provisioning mode. Or should we do that for you? Let us know!