Detecting Intune Policy Processing Problems with PowerShell · CloudEngineerHub
Detection & Remediation10 min read4 views
Detecting Intune Policy Processing Problems with PowerShell
Detect 'assigned but not applied' Intune policies: verify sync freshness, surface MDM admin error patterns, and trigger the sync task safely with PowerShell.
C
CloudEngineerHub Team
Published: September 13, 2026· Last Updated: September 28, 2026·10 min read
There is a specific kind of Intune failure that never shows up as an error anywhere
obvious: the device is enrolled, it checks in on paper, and yet the policies you assigned
last week are simply not on it. The MDM policy pipeline has three distinct health
questions — is the device syncing at all, is sync succeeding, and is what arrived actually
being applied — and the middle and third questions are where "assigned but not applied"
problems hide. This post covers policy application health: sync freshness as a
prerequisite, the MDM diagnostics admin channel for application errors, and the
PolicyManager hive where ingested policy state lives.
Visual Walkthrough
The engine behind every “Detect with PowerShell” runbook on this site.
Exit 1 flips the device into the remediation queue for this package.
remediate
3
Remediation executes
Fix script runs silently as SYSTEM with a 5-minute budget.
verify
4
Re-run detection
The same detection script must now exit 0 — proof, not hope.
telemetry
5
Results report
Per-device outcome lands in Reports → Remediations for trend analysis.
6
Repeat on schedule
The loop continues — drift gets caught the day it appears.
Continuous cycle
Proactive Remediations — The Detect → Fix → Verify Loop. 1. Detection script runs: Scheduled (daily/hourly): exit 0 healthy, exit 1 unhealthy — nothing more. — 2. Verdict: unhealthy: Exit 1 flips the device into the remediation queue for this package. — 3. Remediation executes: Fix script runs silently as SYSTEM with a 5-minute budget. — 4. Re-run detection: The same detection script must now exit 0 — proof, not hope. — 5. Results report: Per-device outcome lands in Reports → Remediations for trend analysis. — 6. Repeat on schedule: The loop continues — drift gets caught the day it appears.
C
CloudEngineerHub Team
Author
The CloudEngineerHub editorial team — Microsoft endpoint management and cloud engineers writing hands-on guides on Intune, Azure, Entra ID, Autopilot, Defender and PowerShell. Every article is reviewed against current Microsoft documentation.
One boundary matters before anything else: the companion topic
intune-powershell-detect-policy-sync-failure covers sync scheduling — whether the sync
task is wired up and firing on its intended cadence. This article assumes the scheduling
side and looks at what policy processing does with each sync. You get a detection script
that checks all three signals plus a remediation that triggers the documented sync task
and verifies a new cycle actually started.
Symptoms
Policy application problems are usually discovered by comparison, not by alerts:
A policy shows "applied" for most devices but stays pending or stale on a few.
A device ignores settings you know were assigned; the settings catalog shows no local trace of them.
The device syncs when you force it from the admin center, then goes quiet for days between syncs.
Entra join and sign-in work, so identity is fine — only management state looks wrong.
Troubleshooting on the device shows MDM diagnostics admin errors you cannot map to a change.
Possible Causes
Stale sync as the root prerequisite
Everything downstream of sync is lies if sync is stale. The enrollment client's recurring
sync task — Schedule #3 created by enrollment client, one per enrollment GUID under
\Microsoft\Windows\EnterpriseMgmt\ — is the local heartbeat, and its last run time is
the honest answer to "when did this device last talk policy?".
Application errors in the MDM diagnostics channel
The DeviceManagement-Enterprise-Diagnostics-Provider/Admin log is where the MDM stack
records policy processing errors. A run of admin-level error events in the last few days
is the most direct evidence that sync arrived but application failed for specific CSPs or
policies. The events deserve your eyes; this script reports their patterns rather than
guessing at meanings.
Missing or empty PolicyManager state
HKLM:\SOFTWARE\Microsoft\PolicyManager\current is where ingested MDM policy state is
materialized. A missing hive or an empty subkey list on an enrolled device means the
pipeline delivered nothing durable — the application stage never completed.
Partial CSP support
Not every CSP applies on every Windows build or edition. A device can be perfectly
healthy and still report policy application errors for CSPs its build does not carry.
That is why the error patterns are reported for triage rather than auto-condemned.
Enrollment state damage
Damaged enrollment GUID folders break the task set beneath them. When the sync task
itself is missing, the problem is upstream of policy processing entirely.
Detection Script
Read-only, seconds to run: enrollment check, sync task freshness against a configurable
age threshold, admin-level MDM error events over the last N days, and a PolicyManager
presence-and-content check.
PowerShell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#Requires -RunAsAdministrator
[CmdletBinding()]
param(
[int]$MaxSyncAgeHours = 24,
[int]$ErrorEventDays = 7
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
<#
.SYNOPSIS
Detection script: checks the health of the MDM policy pipeline - sync
freshness, MDM diagnostics admin errors, and PolicyManager state.
.NOTES
Intended for Intune Scripts and Remediations, 64-bit SYSTEM context.
This script NEVER changes the system. Exit 0 = recent sync, no admin
error events in the window, PolicyManager state present,
1 = policy processing problem found (findings listed in the log),
2 = device is not MDM enrolled, 3 = unexpected error.
This post covers policy APPLICATION health. The companion topic
intune-powershell-detect-policy-sync-failure covers sync SCHEDULING.
Error events are reported as raw message patterns - event IDs are shown
but never interpreted, because meaning belongs to your own triage and to
Microsoft's current documentation.
#>$logDir = 'C:\ProgramData\Company\Logs'$logFile = Join-Path $logDir'PolicyProcessingDetection.log'$syncTaskName = 'Schedule #3 created by enrollment client'$policyManagerCurrent = 'HKLM:\SOFTWARE\Microsoft\PolicyManager\current'function Write-DetectionLog {
param([Parameter(Mandatory)][string] $Level, [Parameter(Mandatory)][string] $Message)
if (-not (Test-Path $logDir)) { New-Item -Path$logDir -ItemType Directory -Force | Out-Null }
"$([DateTime]::UtcNow.ToString('o')) [$Level] $Message" | Out-File -FilePath$logFile -Append -Encoding utf8
}
function Test-MdmEnrolled {
$enrollPath = 'HKLM:\SOFTWARE\Microsoft\Enrollments'if (-not (Test-Path $enrollPath)) { return$false }
$keys = Get-ChildItem -Path$enrollPath -ErrorAction SilentlyContinue
foreach ($keyin$keys) {
$type = (Get-ItemProperty -Path$key.PSPath -Name EnrollmentType -ErrorAction SilentlyContinue).EnrollmentType
if ($type -eq6) { return$true }
}
return$false
}
function Get-SyncTaskInfo {
# Returns the newest 'Schedule #3 created by enrollment client' task info # across all EnterpriseMgmt GUID subfolders, or $null when none exists.$tasks = @(Get-ScheduledTask -TaskPath'\Microsoft\Windows\EnterpriseMgmt\*' -TaskName$syncTaskName -ErrorAction SilentlyContinue)
if ($tasks.Count -eq0) { return$null }
$newest = $nullforeach ($taskin$tasks) {
try {
$info = Get-ScheduledTaskInfo -TaskName$task.TaskName -TaskPath$task.TaskPath -ErrorAction Stop
}
catch {
Write-DetectionLog 'WARN'"Sync task under $($task.TaskPath) exists but run info could not be read: $($_.Exception.Message)"continue
}
if ($null -eq$newest -or$info.LastRunTime -gt$newest.LastRunTime) { $newest = $info }
}
return$newest
}
function Get-MdmAdminErrorPattern {
param([Parameter(Mandatory)][int] $Days)
$filter = @{
LogName = 'Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin'
Level = 2
StartTime = (Get-Date).AddDays(-$Days)
}
$events = $nulltry {
$events = @(Get-WinEvent -FilterHashtable$filter -ErrorAction Stop)
}
catch {
# "No events found" lands here and is the healthy case.return @()
}
$patterns = New-Object System.Collections.Generic.List[string]
foreach ($groupin ($events | Group-Object -Property Id)) {
$first = @($group.Group)[0]
$excerpt = ''if ($null -ne$first.Message) { $excerpt = ($first.Message -replace'\s+', ' ').Trim() }
if ($excerpt.Length -gt160) { $excerpt = $excerpt.Substring(0, 160) + '...' }
$patterns.Add("Id=$($group.Name) Count=$($group.Count) Sample='$excerpt'")
}
return$patterns
}
try {
Write-DetectionLog 'INFO''Policy processing detection run started.' # ---- Check 1: MDM enrollment prerequisite ----$enrolled = Test-MdmEnrolled
if (-not $enrolled) {
Write-DetectionLog 'ERROR''Device has no MDM enrollment (no EnrollmentType 6 under HKLM:\SOFTWARE\Microsoft\Enrollments) - policy pipeline checks are not applicable.'
exit 2
}
Write-DetectionLog 'INFO''MDM enrollment present.' # ---- Check 2: sync task freshness ----$findings = New-Object System.Collections.Generic.List[string]
$syncInfo = Get-SyncTaskInfo
if ($null -eq$syncInfo) {
$findings.Add('MDM sync task not found under EnterpriseMgmt')
Write-DetectionLog 'ERROR'"MDM sync task '$syncTaskName' not found - the enrollment client task set is missing or damaged."
}
elseif ([datetime]$syncInfo.LastRunTime -lt [datetime]'2000-01-01') {
$findings.Add('MDM sync task has never run')
Write-DetectionLog 'ERROR'"MDM sync task exists but has never run."
}
else {
$ageHours = ((Get-Date) - [datetime]$syncInfo.LastRunTime).TotalHours
if ($ageHours -gt$MaxSyncAgeHours) {
$findings.Add("MDM sync is stale: last run $(Get-Date ($syncInfo.LastRunTime) -Format s) ($([math]::Round($ageHours, 1)) hours ago, threshold $MaxSyncAgeHours)")
Write-DetectionLog 'ERROR'"MDM sync is stale: last run $([math]::Round($ageHours, 1)) hours ago (threshold $MaxSyncAgeHours)."
}
else {
Write-DetectionLog 'INFO'"MDM sync is fresh: last run $([math]::Round($ageHours, 1)) hours ago."
}
}
# ---- Check 3: MDM diagnostics admin error events (message patterns only) ----$errorPatterns = Get-MdmAdminErrorPattern -Days$ErrorEventDaysif ($errorPatterns.Count -eq0) {
Write-DetectionLog 'INFO'"No admin-level MDM error events in the last $ErrorEventDays days."
}
else {
foreach ($patternin$errorPatterns) {
Write-DetectionLog 'ERROR'"MDM admin error event pattern (last $ErrorEventDays days): $pattern"
}
$findings.Add("MDM admin error events present in the last $ErrorEventDays days (patterns logged)")
}
# ---- Check 4: PolicyManager current hive ----if (-not (Test-Path $policyManagerCurrent)) {
$findings.Add('PolicyManager\current hive missing')
Write-DetectionLog 'ERROR'"$policyManagerCurrent does not exist - ingested policy state is absent."
}
else {
$subkeys = @(Get-ChildItem -Path$policyManagerCurrent -ErrorAction SilentlyContinue)
if ($subkeys.Count -eq0) {
$findings.Add('PolicyManager\current exists but has no subkeys')
Write-DetectionLog 'ERROR'"$policyManagerCurrent exists but contains no policy subkeys - ingested policy state is empty."
}
else {
Write-DetectionLog 'INFO'"PolicyManager\current present with $($subkeys.Count) subkey(s)."
}
}
if ($findings.Count -gt0) {
Write-DetectionLog 'ERROR'"Policy processing findings: $($findings -join ' | ')"
exit 1
}
Write-DetectionLog 'INFO''Policy pipeline checks passed.'
exit 0
}
catch {
Write-DetectionLog 'ERROR'"Unexpected failure: $($_.Exception.Message)"
exit 3
}
finally {
Write-DetectionLog 'INFO''Policy processing detection run finished.'
}
Detection Logic
The script evaluates the pipeline in dependency order:
Enrollment prerequisite. It enumerates HKLM:\SOFTWARE\Microsoft\Enrollments for a
GUID subkey with EnrollmentType of 6. Without MDM enrollment the rest of the
checks are meaningless, so the script exits with its own code instead of producing
noise.
Sync freshness. It enumerates scheduled tasks under
\Microsoft\Windows\EnterpriseMgmt\* named Schedule #3 created by enrollment client,
takes the most recent LastRunTime across enrollment GUIDs, and compares the age
against $MaxSyncAgeHours (default 24). A missing task, a never-run sentinel value,
or a stale last-run all count as findings.
MDM admin error events.Get-WinEvent filters the
DeviceManagement-Enterprise-Diagnostics-Provider/Admin log to level 2 (error) events
within $ErrorEventDays (default 7). Findings are grouped by event ID, and each
pattern is logged with its count and a whitespace-collapsed message excerpt. The
script deliberately does not interpret IDs — meanings change across builds, and the
excerpt plus your triage is worth more than a hardcoded guess.
PolicyManager state.HKLM:\SOFTWARE\Microsoft\PolicyManager\current must exist
and carry at least one subkey on an enrolled device. Missing or empty is a finding.
Any finding flips the exit to 1, and the final ERROR line lists all findings in one
place so a log read answers the whole question.
Remediation Script
The remediation does one thing: it triggers the documented sync task and verifies that a
new cycle actually started. It refuses to act when the sync is already fresh or the task
is currently running, it polls LastRunTime for up to $VerificationTimeoutSeconds
(default 180) to confirm the run began, and it exits non-zero if the trigger produced no
confirmed cycle — which usually means connectivity or enrollment needs the dedicated
posts' attention. It never deletes PolicyManager state, never re-enrolls, and never
reboots.
PowerShell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#Requires -RunAsAdministrator
[CmdletBinding()]
param(
[int]$MaxSyncAgeHours = 24,
[int]$VerificationTimeoutSeconds = 180
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
<#
.SYNOPSIS
Remediation script: triggers the MDM sync task ('Schedule #3 created by
enrollment client') when the last sync is stale, and verifies that a new
sync cycle started.
.NOTES
Intended for Intune Scripts and Remediations, 64-bit SYSTEM context.
Safety model:
- Starts ONLY the documented enrollment client sync task.
- Skips the trigger entirely when the sync is already fresh (within
$MaxSyncAgeHours) or when the task is currently running.
- Verifies by polling the task's LastRunTime; it does not reboot,
re-enroll, delete policy state, or touch anything else.
Exit 0 = sync triggered and verified (or already fresh / already running),
1 = sync did not confirm within the verification window,
2 = sync task not found (device may not be MDM enrolled - fix enrollment
first), 3 = unexpected error.
#>$logDir = 'C:\ProgramData\Company\Logs'$logFile = Join-Path $logDir'PolicyProcessingRemediation.log'$syncTaskName = 'Schedule #3 created by enrollment client'function Write-RemediationLog {
param([Parameter(Mandatory)][string] $Level, [Parameter(Mandatory)][string] $Message)
if (-not (Test-Path $logDir)) { New-Item -Path$logDir -ItemType Directory -Force | Out-Null }
"$([DateTime]::UtcNow.ToString('o')) [$Level] $Message" | Out-File -FilePath$logFile -Append -Encoding utf8
}
function Get-SyncTask {
$tasks = @(Get-ScheduledTask -TaskPath'\Microsoft\Windows\EnterpriseMgmt\*' -TaskName$syncTaskName -ErrorAction SilentlyContinue)
if ($tasks.Count -eq0) { return$null }
$newest = $nullforeach ($taskin$tasks) {
$info = $nulltry { $info = Get-ScheduledTaskInfo -TaskName$task.TaskName -TaskPath$task.TaskPath -ErrorAction Stop }
catch { continue }
if ($null -eq$newest) { $newest = $task }
else {
$existingInfo = Get-ScheduledTaskInfo -TaskName$newest.TaskName -TaskPath$newest.TaskPath -ErrorAction SilentlyContinue
if ($null -ne$info -and$null -ne$existingInfo -and$info.LastRunTime -gt$existingInfo.LastRunTime) { $newest = $task }
}
}
return$newest
}
try {
Write-RemediationLog 'INFO''Policy processing remediation run started.' # ---- Locate the sync task ----$syncTask = Get-SyncTask
if ($null -eq$syncTask) {
Write-RemediationLog 'WARN'"Sync task '$syncTaskName' not found under EnterpriseMgmt. If the device is not MDM enrolled, fix enrollment first; no sync can be triggered."
exit 2
}
$info = Get-ScheduledTaskInfo -TaskName$syncTask.TaskName -TaskPath$syncTask.TaskPath -ErrorAction Stop
$lastRun = [datetime]$info.LastRunTime
$taskState = (Get-ScheduledTask -TaskName$syncTask.TaskName -TaskPath$syncTask.TaskPath).State
if ($taskState -eq'Running') {
Write-RemediationLog 'INFO''Sync task is already running - letting it finish.'
exit 0
}
$ageHours = 999999.0if ($lastRun -gt [datetime]'2000-01-01') { $ageHours = ((Get-Date) - $lastRun).TotalHours }
if ($ageHours -lt$MaxSyncAgeHours) {
Write-RemediationLog 'INFO'"Sync is fresh (last run $([math]::Round($ageHours, 1)) hours ago) - no trigger needed."
exit 0
}
# ---- Trigger the sync ----
Write-RemediationLog 'INFO'"Triggering sync task (last run $([math]::Round($ageHours, 1)) hours ago)."
Start-ScheduledTask -TaskName$syncTask.TaskName -TaskPath$syncTask.TaskPath -ErrorAction Stop
# ---- Verify a new sync cycle started by polling LastRunTime ----$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$confirmed = $falsewhile ($stopwatch.Elapsed.TotalSeconds -lt$VerificationTimeoutSeconds) {
Start-Sleep -Seconds10$poll = Get-ScheduledTaskInfo -TaskName$syncTask.TaskName -TaskPath$syncTask.TaskPath -ErrorAction SilentlyContinue
if ($null -ne$poll -and [datetime]$poll.LastRunTime -gt$lastRun) { $confirmed = $true; break }
}
if ($confirmed) {
$after = Get-ScheduledTaskInfo -TaskName$syncTask.TaskName -TaskPath$syncTask.TaskPath -ErrorAction SilentlyContinue
$resultText = 'unknown'if ($null -ne$after) { $resultText = [string]$after.LastTaskResult }
Write-RemediationLog 'INFO'"Sync cycle started and verified (new LastRunTime recorded, current result $resultText)."
exit 0
}
Write-RemediationLog 'ERROR'"Sync task was started but no new run was confirmed within $VerificationTimeoutSeconds seconds. Check connectivity (see the connectivity, proxy, and DNS posts) and the enrollment state."
exit 1
}
catch {
Write-RemediationLog 'ERROR'"Remediation failed: $($_.Exception.Message)"
exit 3
}
finally {
Write-RemediationLog 'INFO''Policy processing remediation run finished.'
}
That restraint is the point: triggering a sync is the safe, reversible push the pipeline
needs to retry application. Deleting policy state to "force a refresh" is a destructive
act that belongs in a controlled, one-device-at-a-time troubleshooting session, not in an
automated script.
Deploying Through Intune
Deploy both scripts as one remediation package:
In the Microsoft Intune admin center, go to Devices → Scripts and remediations
and choose + Add on the Windows 10/11 tab.
On Basics, name it Detect or trigger MDM policy processing health and state the
blast radius ("starts the enrollment client sync task when sync is stale; never
deletes policy state").
On Detection script, upload detect.ps1. On Remediation script, upload
remediate.ps1. Set Run script in 64-bit PowerShell: Yes and Run this script
using signed-on credentials: No for both.
Leave Enforce script signature check: No unless your organization signs internal
scripts and deploys the certificates.
On Schedule, Every 1 day fits the default 24-hour sync age threshold. If you
tighten $MaxSyncAgeHours, tighten the schedule to match.
On Assignments, target a test ring first, then devices with reported
"assigned but not applied" symptoms. Avoid assigning to groups that contain
intentionally unmanaged or pending-retirement devices — exit 2 noise is not signal.
Review and create.
Post-specific notes:
Run in 64-bit PowerShell: Yes; signed-on credentials: No. The diagnostics log
and PolicyManager hive are machine-scoped.
The default 24-hour threshold intentionally tolerates Intune's randomized sync
cadence; a device that syncs every 8 hours with jitter should never trip it.
For devices where detection keeps firing after remediation, walk the chain:
connectivity and proxy checks, then the sync-failure companion post, then enrollment
repair.
Visual Walkthrough
Watch a policy travel from console to device and back as compliance telemetry.
Settings Catalog, template or ADMX ingestion — the console is the single source of intent.
targets
2
Assign with targeting
Device or user groups, filters include/exclude, optional scope tags for RBAC.
check-in
3
Device checks in
Push notification or the 8-hour scheduled cycle wakes the MDM channel.
4
CSP evaluation
Enterprise policy hits the Configuration Service Provider; unsupported settings are skipped, not fatal.
telemetry
5
State reports back
Per-setting success/error lands in Intune reports within minutes of check-in.
continuous
6
Drift re-mediated
Next cycle re-applies the intent — the loop keeps the fleet converged.
The Intune Policy Lifecycle — Console → Device → Telemetry. 1. Author the policy: Settings Catalog, template or ADMX ingestion — the console is the single source of intent. — 2. Assign with targeting: Device or user groups, filters include/exclude, optional scope tags for RBAC. — 3. Device checks in: Push notification or the 8-hour scheduled cycle wakes the MDM channel. — 4. CSP evaluation: Enterprise policy hits the Configuration Service Provider; unsupported settings are skipped, not fatal. — 5. State reports back: Per-setting success/error lands in Intune reports within minutes of check-in. — 6. Drift re-mediated: Next cycle re-applies the intent — the loop keeps the fleet converged.
Exit Codes
Intune treats any non-zero detection result as "remediation needed", which triggers the
remediation script per your schedule.
Code
Meaning
0
Detection: fresh sync, no admin error events, PolicyManager state present — Remediation: sync triggered and verified, or already fresh
1
Detection: one or more policy processing findings (listed in log) — Remediation: sync did not confirm within the verification window
2
Device is not MDM enrolled, or the sync task set is missing — fix enrollment, not this script
3
Unexpected script error — inspect the log
Logging
Detection writes to C:\ProgramData\Company\Logs\PolicyProcessingDetection.log,
remediation to PolicyProcessingRemediation.log. Lines carry a UTC timestamp, a level,
and a message. Detection logs the enrollment state, sync age, one line per MDM error
pattern, the PolicyManager subkey count, and a consolidated findings line. Remediation
logs the trigger decision, the trigger itself, and the verification verdict. A few lines
per run; standard rotation applies if you keep long history.
Troubleshooting the Script
Exit 2 on a device you know is enrolled. Re-check the Enrollments key manually; a
device that was re-enrolled recently can have a new GUID while old folders remain.
The script looks for EnrollmentType 6 correctly, but state can be half-migrated.
Error event patterns you cannot explain. Take the logged excerpt — ID, count,
message text — and check it against Microsoft's MDM error documentation for your
build. The script intentionally leaves interpretation to you.
Remediation confirms a run but detection still exits 1. The sync ran; the
application errors or PolicyManager gap persist. That combination points at the CSP
or policy content, not at scheduling — and the deep-dive is manual.
Get-WinEvent blocked by hardening tooling. Some agents restrict event log reads
for SYSTEM scripts. Have your security team approve the log name before rollout.
Exit 3 with a task error. Confirm 64-bit PowerShell and that the Task Scheduler
service is running.
How to Validate the Fix
On a healthy device, run detection in an elevated 64-bit PowerShell console. Expect
exit 0 with a sync age line under the threshold and "No admin-level MDM error
events".
In a lab VM, set the system clock forward past $MaxSyncAgeHours (or simply wait for
a genuinely stale device), run detection, and expect exit 1 with the staleness
finding.
Run remediation manually. Expect exit 0 with a "Sync cycle started and verified"
line, then re-run detection and expect the staleness finding gone.
On a device with real application errors, compare the logged patterns with the
policies assigned to it in the admin center, and confirm the offending policy in the
device's policy report view.
In the admin center, confirm device status for the package reflects the expected
result after the next sync.
Production Considerations
Execution context. Both scripts run as SYSTEM in 64-bit Windows PowerShell. The
only change remediation makes is starting an existing, documented task.
Idempotency. Detection is read-only. Remediation is gated three ways — no action
on fresh syncs, no action on running tasks, and verification after the trigger.
Side effects and reboots. Triggering a sync makes the device contact the management
service and re-evaluate policy; that is normal operational traffic. Nothing in either
script reboots, re-enrolls, or deletes state.
Security review notes. No credentials or secrets are involved. Event excerpts are
written to the local log only. Have your PowerShell automation SME and security team
review both scripts and validate them in a test ring before broad production rollout,
consistent with your script signing and review policy.
Scope discipline. This post covers policy application health; sync scheduling is
the companion topic. Deploying both packages side by side gives you the full pipeline
picture without either script overreaching.
How is this different from the policy sync failure post?
That post covers sync scheduling — whether the sync task is configured and firing on its
cadence. This post covers policy application: after sync arrives, did the device actually
process it, or did the MDM stack log errors and drop the state? The two fail
independently, and the fixes differ, which is why they get separate scripts.
Why doesn't the script interpret the event IDs it finds?
Event meanings vary across Windows builds and service updates, and a hardcoded
"Id 1085 means X" claim goes stale and misleads triage. The script reports the ID, its
count, and a message excerpt — the raw evidence — and leaves interpretation to current
documentation and your own comparison with assigned policies.
Is triggering the sync task safe to automate?
Yes, with the gates this script applies: it only starts the documented enrollment client
sync task, only when sync is stale or the task never ran, and it verifies the cycle
started. A sync is the same push the admin center's Sync button performs — the script
just makes it available at fleet scale with an audit trail.
Comments
0No comments yet — be the first to share your thoughts.