HomeNewsletterCommunityMVP FeedToolsArchiveBlogToday's NewsAboutServicesQuick Links Subscribe free
← Back to Blog
Autopilot AutopilotIntunePlatform ScriptsIntune Management ExtensionDevice PreparationPowerShellWin32 AppsEnrollment Status Page

Intune Platform Scripts Have No Guaranteed Order - and Autopilot Will Prove It

IA
Imran Awan
21 August 2026

There is a deployment pattern that looks completely reasonable on a whiteboard. Script A creates a folder and drops a configuration file into it. Script B reads that file and applies the settings. Two small platform scripts, one clean dependency, done before lunch.

It works on every pilot device. Then you scale to four thousand machines and a slice of them arrive at the desktop half-configured. Not all of them. Not the same ones twice. That is the signature of an ordering assumption, and Intune platform scripts are one of the easiest places in the endpoint stack to make one by accident.

The short version

Microsoft documents that platform scripts run before Win32 apps, and documents the phase order inside an Autopilot device preparation deployment. Microsoft documents no execution order at all between one platform script and another. That absence is the whole point: if your provisioning depends on script A finishing before script B starts, nothing in the product guarantees it, and the failure will be intermittent rather than obvious. Make every script self-contained, or move the ordering into Win32 app dependencies where Intune actually enforces a relationship.

The problem: script chains that only break at scale

Intune platform scripts are the PowerShell scripts feature you find under Devices in the admin center. They are delivered and executed by the Intune Management Extension, which Microsoft abbreviates as the IME. For the rest of this post I will use both names, because the portal says "platform scripts" and every log file on the device says IME.

The dependency chain is the most common way admins get hurt here. It usually looks like one of these:

Every one of those pairs runs fine when the device is quiet. The pilot device has nothing else competing for the IME, so the scripts effectively run one after another and the chain holds. During real provisioning the device is doing a dozen other things at once. The order shifts, and the second script runs against a prerequisite that does not exist yet.

Here is the shape of the failure, as it appears in the second script.

PowerShell — Script B, the one that assumes Script A already ran
# The assumption: Script A created this folder and wrote config.json into it. $config = Get-Content 'C:\ProgramData\Contoso\Provisioning\config.json' -Raw # On a quiet pilot device this succeeds. During real provisioning it can throw # ItemNotFoundException, because nothing guarantees Script A finished first. Set-ItemProperty -Path 'HKLM:\SOFTWARE\Contoso\Agent' -Name 'Tier' -Value $config.Tier # Worse: this writes nothing useful if $config parsed as empty, and the script # still exits 0. Intune reports Success and you never hear about it.

Notice the second failure mode. A missing prerequisite that throws is annoying but visible. A missing prerequisite that produces an empty value and a zero exit code is much worse, because Intune records the script as successful. Your reporting says the fleet is configured. It is not.

Gotcha: Intune reports a platform script as successful based on the exit code, not on whether the script achieved anything. A script that skips its own work and exits 0 is indistinguishable from a script that did the job. This is why ordering bugs hide for months.

Why it happens: what Microsoft documents, and what it does not

The honest answer to "what order do platform scripts run in" is that Microsoft does not say. Before you treat that as a gap in my research, look at how much Microsoft does document about script execution. The specificity everywhere else is exactly what makes the silence on ordering meaningful.

What is documented

BehaviourWhat Microsoft documentsSource doc
Run frequency"Once the script executes, it doesn't execute again unless there's a change in the script or policy."Platform scripts
Retry on failureRetried three times, on the next three consecutive IME check-ins. After that, no further attempts unless the script changes.Platform scripts
TimeoutScripts time out after 30 minutes.Platform scripts
Size limitThe script must be less than 200 KB (ASCII).Platform scripts
Check-in cadenceThe IME checks for new or updated installations every 8 hours, independently of the MDM check-in. It also checks after every reboot.IME overview
Execution context"Run this script using the logged on credentials" defaults to Yes. Choose No for system context.Platform scripts
Host architecture"Run script in 64-bit PowerShell host" defaults to No, which means a 32-bit host.Platform scripts
Per-user re-runScripts assigned to the device run for every new user that signs in, except on multi-session SKUs where user check-in is disabled.Platform scripts
Scripts versus Win32 apps"PowerShell scripts are executed before Win32 apps run. In other words, PowerShell scripts execute first. Then, Win32 apps execute."Platform scripts
ESP tracking"During ESP, SideCar tracks only Win32 apps (no PowerShell scripts)."ESP troubleshooting

That last row is the one most people get wrong, so it is worth stating slowly. SideCar is Microsoft's internal name for the IME acting as an Enrollment Status Page policy provider. During a classic Autopilot deployment with the Enrollment Status Page shown, the page counts and blocks on Win32 apps. It does not count or block on platform scripts.

Context: Combine those two documented facts and you get the behaviour that surprises people. Platform scripts start before Win32 apps, but the Enrollment Status Page only waits for the Win32 apps. So a slow script delays the app phase, yet the page can finish and hand the user a desktop while that script is still running in the background.

Autopilot device preparation is different, and it is documented

Autopilot device preparation, sometimes called Autopilot v2, does not use the Enrollment Status Page at all. Microsoft states that plainly: "Windows Autopilot device preparation doesn't use the Enrollment Status Page (ESP)." Instead it runs its own tracked sequence, and that sequence is published. Microsoft documents device setup continuing in this order.

  1. The device joins Microsoft Entra ID and enrols in Intune.
  2. The Intune Management Extension installs.
  3. The user is added to, or removed from, the local Administrators group according to the policy.
  4. The deployment syncs with Intune and checks for selected line-of-business and Microsoft 365 apps. All MDM policy is synced at this point too, but "application of the policy isn't tracked during the deployment."
  5. Selected line-of-business and Microsoft 365 apps install. A failure here fails the deployment.
  6. Selected PowerShell scripts run. A failure here fails the deployment.
  7. Selected Win32, Microsoft Store and Enterprise App Catalog apps install. A failure here fails the deployment.
  8. The "Required setup complete" page is shown, the user is signed in, and a second sync delivers everything else.

Read step 6 carefully. It says the selected scripts run, and that a failure fails the deployment. It does not say in what order the selected scripts run relative to each other. That is the gap, and it is the same gap as in classic Autopilot.

Gotcha: Step 8 of the documented flow is the one that catches teams out. Apps and scripts assigned to the device group but not explicitly selected in the device preparation policy are delivered in that second sync, after the user already has a desktop. If your "essential" script is assigned but not selected, it is not part of provisioning at all.

The absence of an ordering guarantee is the finding

Microsoft is not shy about saying when order is undefined. In the Win32 app documentation, describing dependencies, the wording is explicit: "evaluation and installation of dependencies doesn't follow a specific order at a dependency level." That is a product team choosing to tell you an ordering guarantee does not exist.

No equivalent statement exists for platform scripts, in either direction. There is no documented order between scripts, and there is no documented promise of one. The correct engineering response to an undocumented ordering is to assume there is none. An undocumented behaviour that happens to hold today is free to change in the next IME release, and the IME updates itself.

Watch out: Do not try to "fix" ordering by deleting or editing values under the IME policy registry key to force a script to re-run. That key is not documented, the behaviour is not supported, and across a fleet you are creating a state the service does not expect. If a script must run again, change the script content or the policy. That is the documented trigger.

The registry surface, documented and otherwise

Three registry areas matter here. Two are documented by Microsoft. One is not, and I am labelling it clearly rather than presenting it as official. They all sit below this parent key.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft
Subkey below that parentWhat it holdsDocumented?
Windows\Autopilot\EnrollmentStatusTrackingRoot of Enrollment Status Page tracking state. Contains Device, ESPTrackingInfo, and a subkey named for the signed-in user's SID.Yes
Windows\Autopilot\EnrollmentStatusTracking\Device\DevicePreparationInstall state of the IME as a page policy provider, plus the resource types that provider tracks. States are 1 NotInstalled, 2 NotRequired, 3 Completed, 4 Error.Yes
Windows\Autopilot\EnrollmentStatusTracking\Device\Setup\Apps\PolicyProviders\SidecarTrackingPoliciesCreated, showing whether the IME built its tracking policies for the device setup phase.Yes
Windows\Autopilot\EnrollmentStatusTracking\Device\Setup\Apps\Tracking\Sidecar\Win32App_{AppID}InstallationState per tracked Win32 app: 1 NotInstalled, 2 InProgress, 3 Completed, 4 Error. An Error stops the page installing further apps.Yes
Enrollments\{EnrollmentGUID}\FirstSyncThe page settings the device received, including SkipDeviceStatusPage and SkipUserStatusPage, which are set to 0xffffffff when a phase is skipped.Yes
IntuneManagementExtension\Policies\{Owner}\{ScriptID}Per-script state the IME records: a Result string, an ErrorCode, a DownloadCount, and typically ResultDetails carrying the script's output. Owner is the all-zero GUID for device context, or a user object ID for user context.No. Community-known, stable in practice, subject to change without notice.

Notice what is missing from the documented rows. There is a tracked, numbered, per-app state for Win32 apps and nothing equivalent for scripts. The product tracks app ordering because app ordering is a feature. It does not track script ordering because script ordering is not one.

How to verify: read the IME's own record on the device

You cannot verify an ordering guarantee that does not exist. What you can do is observe what actually happened on a device, which is enough to prove or disprove a suspected ordering bug. Start in the portal, then go to the device.

Step 1: check the recorded outcome in the admin center

Every platform script has its own monitoring blade. This tells you whether the IME thinks the script succeeded, not whether it did its job.

intune.microsoft.comDevicesScripts and remediationsPlatform scriptsMonitor › Device status

Here is the full click path to create or inspect a platform script, with the settings that matter for provisioning.

  1. Sign in to the Microsoft Intune admin center.
  2. Select Devices, then Scripts and remediations, then Platform scripts.
  3. Select Add, then Windows 10 and later.
  4. In Basics, enter a name and a description, then select Next.
  5. In Script settings, browse to the script file. It must be under 200 KB of ASCII.
  6. Set Run this script using the logged on credentials to No for anything that runs during provisioning, because no user is signed in during the out-of-box experience.
  7. Set Enforce script signature check according to your signing policy. The default is Yes.
  8. Set Run script in 64-bit PowerShell host to Yes if the script touches 64-bit-only paths or registry views. The default is No, which means a 32-bit host.
  9. Select Next through Scope tags, then assign the policy to a device group under Assignments.
  10. Review the summary and select Add.

For Autopilot device preparation, the selected scripts live in the policy itself.

intune.microsoft.comDevices › WindowsEnrollmentDevice preparation policies

Inside the policy, the Apps section allows up to 25 managed applications and the Scripts section allows up to 10 PowerShell scripts. Both sets must also be assigned to the device group named in the policy, and both must be configured for System context. The out-of-box experience timeout, Minutes allowed before showing installation error, accepts an integer between 15 and 720 and applies to the whole deployment rather than to any individual app or script.

Gotcha: Microsoft's own device preparation pages disagree on the app limit. The policy configuration page says up to 25 managed applications, while the workflow overview page still says up to 10. The script limit is 10 on both. Trust the configuration page, and confirm against your own tenant's UI before you design around a number.

Step 2: read the per-script state on the device

This is the undocumented key. It is where the IME parks its own record of what it ran and what came back.

Registry Editor
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IntuneManagementExtension\Policies
00000000-0000-0000-0000-000000000000  (device / system context)
aaaaaaaa-0b0b-1c1c-2d2d-333333333333   Result = Success   ErrorCode = 0   DownloadCount = 1
bbbbbbbb-0b0b-1c1c-2d2d-333333333333   Result = Fail      ErrorCode = 1   DownloadCount = 4
USER-OBJECT-ID  (user context)
cccccccc-0b0b-1c1c-2d2d-333333333333   Result = Success   ErrorCode = 0   DownloadCount = 1

A DownloadCount of 4 on a failing script is the documented retry behaviour showing itself. That is the original attempt plus three retries, after which the IME stops trying.

Step 3: build the real timeline from the logs

The log folder is documented as C:\ProgramData\Microsoft\IntuneManagementExtension\Logs. Microsoft documents IntuneManagementExtension.log as the main log, carrying check-ins, policy requests, policy processing and reporting. It documents AgentExecutor.log as the log that tracks PowerShell script executions. Both are CMTrace format, so every line carries a timestamp. That timestamp is the only real evidence of order you will ever have.

PowerShell — on the device (run elevated)
$log = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log' Select-String -Path $log -Pattern '\[PowerShell\]', 'exitCode' | Select-Object -Last 20 # The IME tags its own platform-script lines with the [PowerShell] component. # Healthy looks like: policies requested, then a tamper validation pass, then # an agentexecutor.exe launch, then "Powershell execution is done, exitCode = 0". Get-Service IntuneManagementExtension | Select-Object Status, StartType # Must be Running and Automatic. Microsoft notes that a Manual start type may # mean the service does not restart after a reboot, so scripts never get checked.
Context: The IME trims its own logs, and Microsoft notes it also cleans up the temporary output and error files after a script executes. On a device that has been in service for weeks, the Autopilot-era lines are usually long gone. Capture logs from a failing device while it is still failing, not a fortnight later.

Step 4: run the companion script

Reading two sources by hand and correlating GUIDs gets old quickly. The companion script does it for you. It reads the IME policy key, parses the IME logs, and prints both the recorded outcome per script and the chronological order actually observed on that device. It is read-only, it fails loudly rather than reporting a misleading empty result, and it runs on both Windows PowerShell 5.1 and PowerShell 7.

The file is Get-PlatformScriptExecutionOrder.ps1. Optional flags: -IncludeAgentExecutor to add the script-host launches, -MaxEvents 0 to print the full timeline, and -ResolveNames to turn script GUIDs into their Intune display names with a read-only Microsoft Graph call to GET /beta/deviceManagement/deviceManagementScripts.

Tip: The script refuses to run without administrator rights, on purpose. The IME policy key is not readable by a standard user, and an access-denied read looks exactly like "no scripts have ever run". Aborting is the honest answer; a blank table would be a lie.

The fix: designs that do not need an order

There is no setting that makes platform scripts ordered. The fix is a design change, and there are four patterns worth knowing.

Pattern 1: make every script self-contained

This is the default answer and it solves most cases. A platform script should create its own prerequisites, verify its own outcome, and be safe to run twice. It should never assume another script has run.

PowerShell — the same work, with no ordering assumption
$root = 'C:\ProgramData\Contoso\Provisioning' if (-not (Test-Path -LiteralPath $root)) { New-Item -Path $root -ItemType Directory -Force | Out-Null } # Create the prerequisite instead of assuming a sibling script created it. $cfg = Join-Path $root 'config.json' if (-not (Test-Path -LiteralPath $cfg)) { Write-Error 'config.json is absent and this script cannot create it.' exit 1 } # Exit non-zero on a genuinely missing input. That buys you the three documented # retries on the next three IME check-ins, and a Fail in Intune reporting. $tier = (Get-Content -LiteralPath $cfg -Raw | ConvertFrom-Json).Tier if ([string]::IsNullOrWhiteSpace($tier)) { Write-Error 'Tier was empty.'; exit 1 } # Never let an empty value flow into a Set- call and still exit 0. New-Item -Path 'HKLM:\SOFTWARE\Contoso\Agent' -Force | Out-Null Set-ItemProperty -Path 'HKLM:\SOFTWARE\Contoso\Agent' -Name 'Tier' -Value $tier Write-Output ('Tier set to ' + $tier) exit 0 # Idempotent: a second run produces the same end state, which matters because # device-assigned scripts run again for every new user who signs in.
Tip: Failing loudly is a feature, not a defect. A script that exits non-zero when its input is missing gets three documented retries across the next three IME check-ins, and shows as Fail in the portal. A script that swallows the problem and exits 0 gets neither, and your reporting lies to you.

Pattern 2: move the ordering into Win32 app dependencies

When the work genuinely cannot be made independent, stop using platform scripts for it. Package each step as a Win32 app and express the relationship as a dependency. Intune will not install a Win32 app until its dependent apps are installed, which is an enforced relationship rather than a hopeful one.

  1. Package each step with the Microsoft Win32 Content Prep Tool into an .intunewin file.
  2. In the admin center, go to Apps, then All Apps, then Create, and choose Windows app (Win32).
  3. Upload each package and set its install and uninstall commands. Set Install behavior to System.
  4. On the Detection rules page, define a rule that proves the step completed. A registry value or a file the step creates works well. All configured rules must be satisfied for the app to count as installed.
  5. Select Create to add the app, then reopen it and go to the Dependencies page. Dependencies can only be added after the app exists in Intune.
  6. Add the earlier step as a dependency of the later step, and leave Automatically install set to Yes.
  7. Assign only the final app as Required. Intune targets and installs the dependencies for you.

Two documented limits shape this design. There is a maximum of 100 apps in a dependency graph, counting sub-dependencies and the parent app itself. And critically, "evaluation and installation of dependencies doesn't follow a specific order at a dependency level." So build a linear chain, where C depends on B and B depends on A, rather than making both A and B dependencies of C at the same level.

Each dependency also inherits the documented Win32 retry logic, which is three attempts five minutes apart, plus the global reevaluation schedule that follows a 24-hour cadence. Supersedence is the sibling feature for replacing or updating an app, capped at 10 nodes in a supersedence graph.

Watch out: A Win32 app in a dependency relationship cannot be deleted from Intune until the relationship is removed, and Company Portal hides the uninstall button for it even when "Allow available uninstall" is Yes. Build the chain deliberately, because unpicking it later is a multi-step job on a live tenant.

Pattern 3: bounded waiting, never unbounded

Sometimes you cannot avoid waiting on something the script does not control, such as a service reaching a state. Wait with a bound, and let the platform retry rather than sitting there.

Remember the documented 30-minute script timeout. Remember too that during Autopilot device preparation the out-of-box experience timeout covers the entire deployment, not one script. A script that blocks for 25 minutes waiting for a dependency has burned most of a provisioning budget that everything else shares. Wait for a minute or two, then exit non-zero and let the IME retry on the next check-in.

Pattern 4: keep provisioning-critical work small and explicit

In Autopilot device preparation, only the apps and scripts you explicitly select in the policy are part of the tracked deployment. Everything else assigned to that device group arrives in the second sync, after the user has a desktop. So decide what genuinely must be true before first sign-in, select exactly that, and let the rest land afterwards. Fewer selected items means fewer chances for the undefined order between them to matter.

Tip: If two things must happen in order and both must complete before first sign-in, combine them into one script. One script with two ordered sections is guaranteed to run in that order. Two scripts are not.

There is no Group Policy equivalent, and that matters here

Platform scripts are an Intune-only feature, delivered by the IME. There is no Group Policy path that configures them, and no configuration service provider that reorders them.

Context: The nearest Group Policy analogue is startup and logon scripts, at Computer Configuration > Policies > Windows Settings > Scripts (Startup/Shutdown), where the scripts run in the order listed in the policy. That ordering guarantee is exactly what Intune platform scripts do not have. If you are migrating an ordered set of Group Policy startup scripts to Intune, the order does not come with them.

Proof it worked: the observed order on a real device

The companion script was run on a live Intune-managed Windows 11 device. The output below is genuine, with tenant and script identifiers replaced by Microsoft-style placeholders and redaction markers. Treat the specific GUIDs and timestamps as illustrative.

PowerShell — Get-PlatformScriptExecutionOrder.ps1 (identifiers replaced)
== Preflight PowerShell : 5.1.26100.9168 Elevation : administrator IME agent folder : present IME service : Running (start type Automatic) IME policy key : present IME log folder : C:\ProgramData\Microsoft\IntuneManagementExtension\Logs == Recorded outcome per platform script (IME registry state) Source: HKLM\SOFTWARE\Microsoft\IntuneManagementExtension\Policies - NOT documented by Microsoft. Owner ScriptId Result ErrorCode DownloadCount ----- -------- ------ --------- ------------- 00000000-0000-0000-0000-000000000000 aaaaaaaa-0b0b-1c1c-2d2d-333333333333 Success 0 1 00000000-0000-0000-0000-000000000000 bbbbbbbb-0b0b-1c1c-2d2d-333333333333 Success 0 1 USER-OBJECT-ID cccccccc-0b0b-1c1c-2d2d-333333333333 Success 0 2 Owner is the key one level above the script id. The all-zero GUID is the device (system) context; a real GUID is a user context. == Observed order on this device (IME log timeline) Parsing 3 log file(s). Matched 5150 script-related log line(s). First observed activity per script GUID: FirstSeen LastSeen ScriptId Lines --------- -------- -------- ----- 2026-08-21 14:55:20 2026-08-21 14:55:20 aaaaaaaa-0b0b-1c1c-2d2d-333333333333 1 2026-08-21 20:17:52 2026-08-21 20:17:52 bbbbbbbb-0b0b-1c1c-2d2d-333333333333 1 Chronological log events: Time Message ---- ------- 2026-08-21 22:24:29 Powershell execution is done, exitCode = 0 2026-08-21 22:24:34 [PowerShell] Tamper validation pass. 2026-08-21 22:24:39 "C:\Program Files (x86)\Microsoft Intune Management Extension\agen... 2026-08-21 22:24:40 Powershell execution is done, exitCode = 0 2026-08-21 22:24:51 Powershell execution is done, exitCode = 1 # exitCode = 1 is a script reporting failure. That is the retry trigger. Showing the last 10 of 5150 events. Use -MaxEvents 0 for all.

What to take from a run like this. The registry section tells you the outcome the IME recorded, which is what the portal reports. The timeline section tells you when things actually happened, which is the only way to test an ordering theory. If the two scripts you care about show overlapping windows in the timeline, you have your answer. They were not sequential, and any code that assumed they were is running on luck.

Tip: Run this on your three loudest problem devices and on one known-good device, then compare timelines. Ordering bugs almost always show up as a different relative order on the failing devices, and that comparison is far more persuasive to a change board than a theory.

The Event Viewer question

There is no Event ID catalog in this post, and that is deliberate rather than an omission. Microsoft does not document an event log channel or event IDs for platform script execution. The diagnostic surface for the IME is its CMTrace-format log files, which Microsoft suggests reading with CMTrace.exe. The related Autopilot and mobile device management event channels, such as the DeviceManagement-Enterprise-Diagnostics-Provider admin channel, carry enrolment and reboot diagnostics rather than per-script results. Inventing plausible-looking event IDs here would be worse than saying there are none.

References

Microsoft official documentation, all fetched and confirmed while writing this post.

Community deep-dives, each fetched and confirmed on topic.

AuthorPostWhy it is worth reading
Rudy OomsAutopilot and pre-provisioning's infinite waiting listWalks a real case where a platform script blocks during pre-provisioning and the Enrollment Status Page sits on "Identifying apps", precisely because scripts are not tracked.
Peter van der WoudeWorking with Win32 app dependenciesThe practical walkthrough of building an ordered chain with dependencies, including the dependency viewer and its limits.

The short conclusion. Platform scripts are excellent at doing one independent thing well. They are a poor substitute for a dependency graph. If your provisioning correctness rests on which of two scripts wins a race, redesign it before it redesigns your week.

PowerShell — companion script

Download it from Imran76Awan/Windows-Autopilot-Scripts — no sign-in required. It is read-only: it reports and never changes a device or anything in Intune. Validate it in your own environment before relying on the output.

Get-PlatformScriptExecutionOrder.ps1 — Reports which Intune platform scripts (PowerShell scripts) have run on this device, the
View all scripts on GitHub
Was this post helpful?
React below — no account needed
Share this post
LinkedIn X / Twitter Reddit Bluesky

More from EndpointWeekly

Autopilot
Which apps actually block the Enrollment Status Page (and why…
You set the ESP to block until your required apps install, it cleared in four minutes,…
Autopilot
The ESP Hit Its Time Limit, The User Clicked Continue Anyway,…
The Enrollment Status Page time limit is not a patience setting. It is the moment Windows…
Autopilot
Enrollment Configuration Priority: Why Your Second ESP Profile…
Device configuration profiles merge. Device enrollment configurations do not. Exactly one…