You wrote a detection script and a remediation script, uploaded them as a script package under Devices > Manage devices > Scripts and remediations, and assigned it to a device group. A day later the overview shows a pile of devices With issues, nothing Issue fixed, a few stuck at Pending, and the one PC you tested by hand works fine from your own PowerShell window. The usual first moves are to rewrite the script, delete and recreate the package, or reinstall the Intune Management Extension.
Hold off. A Remediation is a short chain: tenant and device are eligible (join type, Windows edition, license verification) → the package is assigned to the right group on a schedule → the Intune Management Extension (IME) is installed and pulls the policy → the detection script runs in the context you picked and exits 0 or 1 → only on exit 1 the remediation runs, then detection runs again → the client reports on its own cycle. Most "remediation script not running" tickets die at the exit code, the run context (SYSTEM and 32-bit with Microsoft's recommended package settings), or a report that simply hasn't caught up. This post walks the chain in order. Every step below only reads until the change table at the end.
If the device is not enrolled at all, use the Auto MDM Enroll Failed (Event 76) triage first. If the device is enrolled but nothing from Intune is landing (policies, apps, scripts all stale), use the Company Portal / device not syncing triage instead. This one is for when everything else lands and the Remediation package is the problem.
TL;DR
-
The remediation script only runs when detection ends with
exit 1. Microsoft is explicit: any other exit code and the remediation script does not run.exit 0means no issue. - "With issues" next to "Issue fixed" is the healthy result. Detection found the problem, the remediation ran, and the detection that ran afterwards found it gone. The worrying pattern is With issues plus Recurred or Failed.
- Microsoft's recommended package settings are Run script in 64-bit PowerShell: No and logged-on credentials depending on the script. "No" for logged-on credentials means the SYSTEM context. A script you tested as yourself in 64-bit PowerShell can read different registry keys (
WOW6432Node), profiles, and drives than it does under Intune. - Remediations need Entra joined or hybrid joined devices on Windows Enterprise, Pro, or Education (or co-managed), eligible Windows licensing, and a tenant admin who turned on I confirm that my tenant owns one of these licenses under Tenant administration > Connectors and tokens > Windows data.
- The client pulls Remediation policy after an IME or device restart, after a user signs in, and every 8 hours. Recurring scripts report on a 7-day cycle: during the first six days the client reports only when something changes. A quiet report doesn't mean a broken script.
- The device-side truth is in
C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\HealthScripts.log(Remediation execution), withAgentExecutor.logandIntuneManagementExtension.lognext to it.
Where in the chain did it break?
| Step | What happens | Typical symptom when it breaks |
|---|---|---|
| 1. Eligibility | Entra joined / hybrid joined; Enterprise/Pro/Education or co-managed; license verification toggle on | Package never shows up on the device; Create script package unavailable for the tenant |
| 2. Assignment | Device (or user) group, filters, schedule (Once / Hourly / Daily) | Device missing from Device status entirely; "not running every hour" |
| 3. IME + policy | IME service installed, current version, pulls policy every 8 hours | Stuck at Pending; nothing in HealthScripts.log for the package |
| 4. Detection runs | Script runs as SYSTEM or user, 32- or 64-bit host, signature check or Bypass | Detection Failed; output says the path/key/module doesn't exist |
| 5. Exit code |
exit 1 = issue found → remediation; exit 0 = no issue; anything else = no remediation |
Devices With issues per output text, but remediation never runs |
| 6. Remediation + re-detect | Remediation runs, then detection runs again | Recurred (fix didn't stick) or remediation Failed (script error) |
| 7. Reporting | Run-once reports after the run; recurring follows the 7-day cycle | Portal hours or days behind what the log shows |
Pick one device you can reach that shows the problem and one that reports as expected. Reading both at each step answers most tickets faster than the overview chart.
Step 1: Read eligibility: join type, edition, and license verification
On the device:
dsregcmd /status | Select-String -Pattern 'AzureAdJoined','DomainJoined','WorkplaceJoined'
(Get-CimInstance Win32_OperatingSystem).Caption
| Result | Meaning |
|---|---|
| AzureAdJoined YES (with or without DomainJoined YES) | Join type is supported for Remediations |
| WorkplaceJoined YES only (Entra registered / BYOD) | The IME itself can run on registered devices, but Microsoft's Remediations prerequisites list Entra joined or hybrid joined only |
| Windows Home | Not in the supported editions (Enterprise, Professional, Education, or co-managed) |
In the tenant, read Tenant administration > Connectors and tokens > Windows data > Windows license verification. Microsoft lists Remediations among the features that need I confirm that my tenant owns one of these licenses set to On (the default is Off). That toggle is a tenant-level attestation (Windows Enterprise E3/E5 or Microsoft 365 F3/E3/E5, Education A3/A5, or Windows VDA E3/E5). It doesn't check or assign individual licenses, so also confirm the users of those devices actually hold one of them. Microsoft also notes that an Intune Service Administrator has to confirm licensing before Remediations are used the first time.
Step 2: Read the assignment, filters, and schedule
Open the package > Properties > Assignments:
| Read | Healthy | Watch for |
|---|---|---|
| Included groups | Contains the test device (or its user) | Device group assigned but the device object isn't a member yet (dynamic group lag) |
| Excluded groups | Doesn't catch the test device | Microsoft: don't mix user and device groups across include and exclude |
| Filter | Evaluates to Include for the device | Filter on a property the device doesn't report the way you expect |
| Schedule | Once (date/time), Hourly (interval under 24 hours), or Daily | Once with a past time; Hourly expected but Daily set; local time vs Use UTC |
| Scope tags | Your role can see the package | Package invisible to the helpdesk role that's meant to run it on demand |
Microsoft documents that a missed scheduled run executes when the device is next online, as soon as possible. "Not running every hour" is often a schedule that says Daily, or a device that was asleep.
Step 3: Read the IME service, version, and check-in
Get-Service -Name IntuneManagementExtension | Select-Object Status, StartType
$ime = "${env:ProgramFiles(x86)}\Microsoft Intune Management Extension\Microsoft.Management.Services.IntuneWindowsAgent.exe"
if (Test-Path $ime) { (Get-Item $ime).VersionInfo.FileVersion } else { 'IME agent exe not found' }
Get-ChildItem 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs' |
Select-Object Name, Length, LastWriteTime
| Result | Meaning |
|---|---|
| Service missing | IME never installed. It installs automatically when a PowerShell script, Win32 app, Remediation, or custom compliance script is assigned and the prerequisites are met |
| Version below 1.58.103.0 | Microsoft: devices on earlier versions don't receive IME-dependent configurations, including remediations |
HealthScripts.log missing or old |
No Remediation has run on this device recently |
Policy retrieval timing, per Microsoft: after a restart of the device or the IME service, after a user signs in, and once every 8 hours from when the IME service started. Company Portal > Settings > Sync (or Sync from Settings or the admin center) starts both an MDM check-in and an IME check-in. Restarting the IntuneManagementExtension service also triggers an IME check-in. A sync is a trigger, not a fix, so use it once and then read the logs.
Step 4: Read HealthScripts.log for this package
Get the package's ID from the admin center (open the script package; the GUID is in the browser address bar), then search the device logs for it:
$id = '00000000-0000-0000-0000-000000000000' # your script package ID
$logs = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs'
Select-String -Path "$logs\HealthScripts*.log" -Pattern $id |
Select-Object -Last 40 | ForEach-Object Line
Select-String -Path "$logs\AgentExecutor*.log", "$logs\IntuneManagementExtension*.log" -Pattern $id |
Select-Object -Last 20 | ForEach-Object Line
CMTrace makes these far easier to read than a raw text editor.
| Log | Microsoft's description | What you're reading for |
|---|---|---|
HealthScripts.log |
Remediation script execution details; also custom compliance scripts and on-demand remediations | Did the package arrive, when did detection/remediation start, what exit code and output came back |
AgentExecutor.log |
PowerShell script execution details | The PowerShell host invocation and errors thrown by the script |
IntuneManagementExtension.log |
Check-in, policy request, processing, reporting | Whether the IME checked in and is reporting results |
No line with your ID at all = Steps 1–3 (eligibility, assignment, IME). Lines with your ID = the package arrived, so the answer is in Steps 5–6.
Step 5: Read the context the script actually runs in
Two package settings decide what your script sees:
| Setting | Value | What the script gets |
|---|---|---|
| Run this script using the logged-on credentials | No | SYSTEM context: machine-wide HKLM and Program Files, no user's HKCU, no mapped drives, no user profile paths |
| Yes | The signed-in user's context and that user's rights (no admin rights for a standard user) | |
| Run script in 64-bit PowerShell | No (Microsoft's recommended setting for packages) |
32-bit PowerShell host on a 64-bit OS: HKLM:\SOFTWARE\... is redirected to WOW6432Node, and System32 to SysWOW64
|
| Yes | 64-bit host on 64-bit Windows | |
| Enforce script signature check | No | Runs with the Bypass execution policy |
| Yes | Uses the device execution policy (Restricted by default on Windows client), needs the signer in Trusted Publishers, and the files must be UTF-8 without BOM |
Reproduce the 32-bit host as an admin on the test device (still read-only, assuming your detection script only reads):
# 32-bit Windows PowerShell on a 64-bit OS
& "$env:windir\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -Command '[Environment]::Is64BitProcess; Test-Path "HKLM:\SOFTWARE\Contoso\App"'
# Same check in the 64-bit host for comparison
& "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -Command '[Environment]::Is64BitProcess; Test-Path "HKLM:\SOFTWARE\Contoso\App"'
If the answers differ, you've found it. For the SYSTEM half, run the same detection script as SYSTEM (for example with Sysinternals PsExec -s -i on a lab device) rather than in your own admin session.
Then read what the device actually received. Community write-ups report that the IME stages Remediation scripts under C:\Windows\IMECache\HealthScripts\ in a folder named after the package ID. Microsoft doesn't document that path, so treat it as a convenience and not a contract:
Get-ChildItem 'C:\Windows\IMECache\HealthScripts' -Recurse -Filter *.ps1 -ErrorAction SilentlyContinue |
Select-Object FullName, Length, LastWriteTime
# UTF-8 BOM check on the file you uploaded (EF BB BF = BOM present)
$b = [System.IO.File]::ReadAllBytes('C:\Temp\Detect_Contoso.ps1')[0..2]
($b | ForEach-Object { '{0:X2}' -f $_ }) -join ' '
Step 6: Read the exit code and output, then decode the status
Run the detection script in the same host Intune uses and read the exit code it really returns:
& "$env:windir\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File 'C:\Temp\Detect_Contoso.ps1'
"Exit code: $LASTEXITCODE"
A detection shape that keeps the report readable: one short line of output, then an explicit exit on every path.
try {
$value = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Contoso\App' -Name 'Setting' -ErrorAction Stop
if ($value -ne 1) { Write-Output "Setting is $value, expected 1"; exit 1 }
Write-Output 'Setting is 1'; exit 0
}
catch {
Write-Output "Key or value missing: $($_.Exception.Message)"; exit 1
}
Whether a missing key counts as "issue found" (exit 1) or "not applicable" (exit 0) is your call. Make it on purpose, because that's the most common reason remediation "never runs."
| What you see | What it means | Read next |
|---|---|---|
| Detection Without issues | Detection exited 0 | If you expected an issue, run detection in the 32-bit / SYSTEM host (Step 5) |
| Detection With issues, Remediation Issue fixed | Detection exited 1, remediation ran, the detection run afterwards came back clean | Nothing. That's success |
| Detection With issues, remediation never ran | No remediation script in the package, or detection didn't actually end with exit 1 (for example the output says "missing" but the script fell through to exit 0) |
Package Settings; $LASTEXITCODE from the test above |
| Remediation Recurred | Remediation ran but detection still finds the issue | Remediation runs in the wrong context/bitness, or something (GPO, app, user) puts the setting back |
| Detection or remediation Failed | Script error or a non-standard exit |
HealthScripts.log / AgentExecutor.log error text; pre-/post-remediation error columns in Device status |
| Pending | Device hasn't run or reported the package yet | Steps 2–3; reporting cycle in Step 7 |
| Not applicable | Assignment filter or applicability excluded the device | Step 2 filter |
| Output column cut off | Output is capped at 2,048 characters | Keep detection output to one line |
The same states are exposed in Microsoft Graph (beta deviceHealthScriptDeviceState): detectionState = unknown, success, fail, scriptError, pending, notApplicable; remediationState = unknown, skipped, success, remediationFailed ("executed successfully but failed to remediate"), scriptError ("encountered an error or timed out"). If you prefer a list to the portal, here's a read-only pull with DeviceManagementConfiguration.Read.All:
Connect-MgGraph -Scopes 'DeviceManagementConfiguration.Read.All'
$id = '00000000-0000-0000-0000-000000000000' # script package ID
$uri = "https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts/$id/deviceRunStates?`$expand=managedDevice"
(Invoke-MgGraphRequest -Method GET -Uri $uri).value |
ForEach-Object { [pscustomobject]@{
Device = $_.managedDevice.deviceName
Detection = $_.detectionState
Remediation = $_.remediationState
LastRun = $_.lastStateUpdateDateTime
LastSync = $_.lastSyncDateTime
PreOutput = $_.preRemediationDetectionScriptOutput } }
(This uses the beta endpoint, which can change. If a page of results comes back with @odata.nextLink, follow it for large deployments.)
A note on 0x87D1FDE8 "Remediation failed": people often search it next to Remediations, but in Microsoft Q&A threads that code shows up against configuration profile / CSP settings, not script packages. Remediations report the detection and remediation states above. If you see 0x87D1FDE8, you're troubleshooting a profile.
Step 7: Read the reporting cycle and the on-demand path
Microsoft's client reporting rules:
| Schedule | When the client reports |
|---|---|
| Run once | After the script runs |
| Recurring (hourly/daily) | Within the first six days, only when a result changes (the first run counts as a change); every seven days regardless |
So a package that fixed the device on Monday and has been "Without issues" since can show the same row for days. Check lastStateUpdateDateTime / lastSyncDateTime (Graph) or the device's Monitor > Remediations blade before you assume it stopped.
Run remediation (preview) on a single device (Devices > By platform > Windows > device > ... > Run remediation) needs:
- Remediations already configured in the tenant (license verification on); the package doesn't have to be assigned
- An Intune admin, or a role with Run remediation under Remote tasks (plus Organization: Read during preview). If the action is missing or greyed out for a helpdesk role, read RBAC and scope tags first
- The device online and able to reach Intune and Windows Push Notification Service (WNS) when you send it. Offline or WNS-blocked devices don't receive the action
- One Run remediation at a time per device. Several in quick succession can overwrite each other
"Run remediation stuck Pending" is usually WNS, an offline device, or a second click that replaced the first.
When a change is justified
| Finding | Prefer | Avoid |
|---|---|---|
Detection never ends with exit 1 when the issue exists |
Fix the exit path; test $LASTEXITCODE in the 32-bit host |
Adding the fix to the detection script "so it runs anyway" |
| Script works as you, fails under Intune | Match the context: 64-bit = Yes for 64-bit paths/registry, or reference WOW6432Node deliberately; logged-on credentials only for HKCU/profile work |
Flipping both settings at random, or granting users local admin |
| Signature check on, script won't run | Sign properly + Trusted Publishers + UTF-8 without BOM, or turn the check off where policy allows | Changing the device execution policy fleet-wide |
| Recurred on many devices | Find what reverts the setting (GPO, app, config profile) and fix the source | Running the remediation hourly to keep winning the fight |
| License verification Off | Have an Intune Service Administrator confirm eligible Windows licensing in Windows data | Toggling it On without the licenses to back it |
| Device not in the assigned group / mixed user+device include/exclude | Correct the group; keep include/exclude the same object type | Assigning to All devices to "make it work" |
| IME missing or below 1.58.103.0 | Fix the device's MDM sync and network; let the IME auto-update | Hand-installing the IME MSI from another source |
| Stuck Pending on one device | One sync or IME service restart, then read HealthScripts.log | Deleting and recreating the package (new ID, reporting history gone) |
| Output truncated | One-line output, under 2,048 characters | Dumping full object output into the report |
| Need a reboot after the fix | Use a separate, scheduled reboot mechanism | Putting reboot commands in detection or remediation scripts (Microsoft says don't) |
FAQ
Why does my Intune remediation script never run when detection runs?
The remediation script only runs if detection exits with exit 1. Any other exit code, including exit 0 or a script that falls through without an explicit exit, means no remediation. Test the real $LASTEXITCODE in the same 32-bit (or 64-bit) host and context Intune uses.
Detection status says "With issues" even after the fix. Is it broken?
Usually not. Detection status records what detection found before remediation. With issues plus Issue fixed means the remediation worked and the detection that ran afterwards came back clean. Recurred or Failed in the remediation column is what needs work.
Where are the Intune remediation logs?
On the device, in C:\ProgramData\Microsoft\IntuneManagementExtension\Logs: HealthScripts.log (Remediation execution), AgentExecutor.log (PowerShell execution), and IntuneManagementExtension.log (check-in and reporting). Search them for the script package ID.
Why is the remediation stuck at Pending?
Either the device hasn't pulled the policy yet (every 8 hours, at IME/device restart, or at user sign-in), it isn't eligible or assigned, or the result hasn't been reported (recurring scripts report on change, and at least every seven days). For on-demand Run remediation, the device must be online and reach WNS.
Should Remediations run in 64-bit PowerShell and as SYSTEM?
Microsoft's recommended package settings are 64-bit No and logged-on credentials depending on the script. "No" for logged-on credentials means SYSTEM. If your script reads 64-bit registry paths or System32 tools, either set 64-bit to Yes or write the script for the 32-bit host on purpose. Use logged-on credentials only when the script has to touch the user's profile or HKCU.
Related posts
- Company Portal can't connect / device not syncing: a 10-minute triage order
- Intune Win32 Required App Install Failed: A Read-First Exit Code and Download Triage
- Intune Win32 app shows Failed after a successful install? It's usually the detection rule
- Auto MDM Enroll Failed (Event 76): A Read-First Intune Group Policy Auto-Enrollment Triage
Free checklist
Remediations are often the cleanup crew that gets devices compliant before you require a compliant device in Conditional Access. If they're quietly not running, enforcing that grant locks real users out. The free Conditional Access pre-enforcement checklist keeps break-glass, pilot groups, and report-only review on one page:
If you want the rest of the Intune runway written down for the team (enrollment hygiene, compliance phasing, break/fix cards and read-only PowerShell snapshots), the Intune & M365 Admin Starter Pack pairs with this triage: https://cashflow4375.gumroad.com/l/joonf?utm_source=devto&utm_medium=article&utm_campaign=aps_first_sale&utm_content=intune-remediation-script-not-running
Admin Pack Studio; not affiliated with Microsoft. Steps 1–7 only read (run only detection scripts that themselves only read). Pilot any package, context, or schedule change on a small device group first, and check current Microsoft docs for Remediations prerequisites and behavior.
Top comments (0)