Automating Intune with PowerShell: 10 Real-World Scripts · CloudEngineerHub
PowerShell12 min read6 views
Automating Intune with PowerShell: 10 Real-World Scripts
Ten proven Intune automation patterns with production-quality PowerShell: stale device cleanup, bulk sync, assignment audits, compliance reporting, and Graph throttling handling.
C
CloudEngineerHub Team
Published: August 28, 2026· Last Updated: October 15, 2026·12 min read
Automating Intune with PowerShell: 10 Real-World Scripts
The Intune admin center is excellent for changing one policy on one Tuesday. It is terrible at doing the same thing 500 times across 40 policies, which is exactly where automation earns its keep. This article walks through ten automation patterns we see working in real enterprises — what each solves, how to build it safely, and three complete, production-quality PowerShell scripts you can run today against the Microsoft Graph API.
Visual Walkthrough
Consent, paging and Retry-After: the difference between a script and a liability.
Interactive FlowGraph API Call — Token to Throttle-Safe Script6/6
1
App registered + consented
Application or delegated permission approved (least privilege).
429 + Retry-After honored — backoff, never hammer.
6
Act + log outcomes
Changes applied, results exported for the runbook.
Graph API Call — Token to Throttle-Safe Script. 1. App registered + consented: Application or delegated permission approved (least privilege). — 2. Token acquired: MSAL fetches access token (cached, refreshed silently). — 3. Call Graph endpoint: HTTPS GET/POST/PATCH with $filter, $select, $top. — 4. Page through results: @odata.nextLink loop for large datasets. — 5. Respect throttling: 429 + Retry-After honored — backoff, never hammer. — 6. Act + log outcomes: Changes applied, results exported for the runbook.
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.
PowerShell 7.4 LTS (works on 5.1, but 7 is faster and cleaner for paging).
The Microsoft Graph PowerShell SDK: Install-Module Microsoft.Graph -Scope CurrentUser.
Intune Administrator role for anything that writes; Global Reader is enough for reporting.
Graph scopes matched to the job: DeviceManagementManagedDevices.Read.All for inventory, DeviceManagementManagedDevices.ReadWrite.All for device actions, DeviceManagementConfiguration.Read.All for policy reads.
A test group of disposable devices before you let any script touch retire, wipe, or sync at scale.
How It Works
Every script in this article follows the same contract: connect with least-privilege scopes, query through Microsoft Graph, shape the data, and write output a human can act on. Read operations use Get-MgDeviceManagementManagedDevice or Invoke-MgGraphRequest with GET. Device actions — sync, retire, wipe — are POST requests to action URLs under /deviceManagement/managedDevices, and every one of them is irreversible enough that you should require an explicit -Confirm or -Force switch before running against production groups.
Two platform realities shape the design: Graph pages results (default 100 rows per call on managed devices), and Graph throttles bursts. Production scripts therefore paginate explicitly and pace themselves with Retry-After awareness.
Step-by-Step Implementation
Here are the ten patterns, each with the problem it solves and the key Graph call:
1. Stale device hygiene
Devices that have not checked in for 60+ days break reporting, inflate license counts, and hide real compliance posture. The fix is a scheduled report (and optionally an automated retire after a longer grace window) driven by lastSyncDateTime.
2. Bulk device sync
After assigning a new policy, you want devices to check in now, not at their next natural cadence. Trigger the sync action per device for a targeted group.
3. Assignment audit
Nobody can answer "which groups does this policy hit?" from memory at 40 policies. Export every configuration and compliance policy with its assignments to a reviewable CSV.
4. Compliance snapshot for leadership
One CSV of noncompliant devices with their error details, refreshed daily, kills the weekly manual count.
5. Primary user gap detection
Devices with no primary user break user-targeted assignments and Self-service flows. Flag them for cleanup.
6. Autopilot hash import at scale
Collect hardware hashes from a file share and import them as Windows Autopilot device identities in a loop, logging duplicates instead of failing the batch.
7. Windows update ring drift report
Compare deferral settings across update rings and output differences, so ring sprawl becomes visible.
8. App assignment matrix
List every Win32 and mobile app with its assignment intent (required, available, uninstall) per group for license and risk reviews.
9. Enrollment profile usage report
Count devices per Windows Autopilot enrollment profile name to catch teams quietly enrolling against the wrong profile.
10. Scheduled change notifications
A wrapper that emails or posts to a Teams webhook whenever a script detects drift — the last mile that makes all the other scripts operational instead of archival.
Patterns 1-3 are fully scripted below; the rest reuse the same scaffolding with different endpoints.
Configuration Deep Dive
Paging. managedDevices caps each page. With the SDK, pass -All and optionally -PageSize 500. With Invoke-MgGraphRequest, loop on @odata.nextLink until it is absent — as the first script below does.
Throttling. On HTTP 429, Graph returns a Retry-After header in seconds. The helper in script 1 honors it, sleeps, and retries; copy that helper into your module rather than reinventing it per script.
Write actions. POST to /deviceManagement/managedDevices/{id}/sync, /retire, or /wipe. Retire removes company data and leaves personal devices usable; wipe returns the device to a clean state. Both return HTTP 204 on success and are asynchronous — the device reflects the change at its next check-in.
Idempotency. Every script must be safe to run twice. Reports are naturally idempotent; write actions get idempotent by checking state first (for example, skip sync if lastSyncDateTime is under an hour old).
Real-World Enterprise Scenario
A 12,000-seat retailer had license true-ups failing because 1,900 "active" devices had not synced in over a year, and a stalled rollout because nobody could say which groups a migrated compliance policy reached. Two scripts changed the culture: the stale-device report now runs nightly and feeds a retire list that operations approve weekly, and the assignment audit runs before every change board, attached to the ticket as evidence. Manual policy archaeology dropped from hours to minutes, and the license count fell by 1,400 rows the first month.
Visual Walkthrough
Terminating vs non-terminating, and the exit code your remediation needs.
Decision TreePowerShell Error Handling — The Honest Fork5/5
Decision gate
Command fails
Catch it deliberately or let the run fail loudly — never silently.
Inside try/catch
1
-ErrorAction Stop
Non-terminating errors promoted so catch actually fires.
2
Catch with context
Log $_.Exception + the operation + target object.
3
Finally cleans up
Sessions/disposables released regardless of outcome.
4
Exit 0 with proof
Detection scripts exit 0 when the check passed.
Uncaught
1
Error stream continues
Non-terminating errors flow on; state may be corrupt.
Use -All with the SDK or loop on @odata.nextLink as in script 1
HTTP 429 floods during bulk retire
Writes not paced
Sleep 300-500 ms per action and honor Retry-After
Retire does nothing visible
Action is asynchronous
Check device state after next sync; do not re-run blindly
Group filter returns empty
displayName filter needs exact match
Verify the name and quote escaping in the $filter clause
403 on /assignments
Missing DeviceManagementConfiguration.Read.All
Reconnect with the required scope
Sync triggers but policy not applied
Assignment filter or group membership wrong
Check the assignment intent and group membership first
Common Errors
"Insufficient privileges" on POST retire. The session was connected with Read.All only. Reconnect with DeviceManagementManagedDevices.ReadWrite.All.
"Resource not found" for a device ID. The device was deleted between enumeration and action. Catch and continue — this is normal in large loops.
Filter syntax errors. The $filter value needs URL-safe quoting; when in doubt, test the exact URI in Graph Explorer first.
CSV shows one row only. You piped the whole object collection into Export-Csv inside the loop; accumulate into an array and export once at the end.
Best Practices
Separate read scripts from write scripts; different files, different scopes, different approvals.
Always export a CSV before any write action so you have a point-in-time record of what the script saw.
Require an explicit -Retire/-Wipe switch; never bundle destructive actions into default behavior.
Pace every write loop; Graph throttling is guaranteed at scale, not a maybe.
Log device IDs, not just names, so you can correlate failures with Graph responses later.
Keep the retry helper in a shared module; copy-pasted helpers drift and hide bugs.
Security Considerations
DeviceManagementManagedDevices.ReadWrite.All can retire or wipe the entire fleet; treat the app registration or automation account as Tier-0 adjacent and protect it with Conditional Access.
Exported CSVs contain usernames and device identifiers — store them in restricted shares with retention rules, not on admin desktops.
Use certificate credentials or managed identity for unattended runs; rotate any client secrets on a schedule.
Review Graph activity via Entra sign-in and service principal logs; alert on retire/wipe volume spikes.
FAQ
Q: Can I retire devices automatically without human approval?
A: Technically yes, but enterprises should gate retirement behind an approval step. The pattern above exports a list, humans approve, then a second run with -Retire executes — auditors love this trail.
Q: Why do I see throttling even though my loop is "fast"?
A: Graph budgets requests per caller per endpoint per time window. A few thousand small GETs in a minute exhausts it. Pace writes at 200-500 ms and read pages in controlled batches.
Q: Does triggering sync immediately apply new policies?
A: Sync forces check-in; the device then downloads assigned policy. Application still depends on the policy itself — some settings require a reboot or user sign-out.
Q: Can I do all of this from the Windows 365 or Compliance portals instead?
A: Portals give you single actions; automation gives you scale, evidence, and repeatability. Use the portal for one-offs and scripts for anything recurring.
Q: Which PowerShell version should I target?
A: PowerShell 7.4 LTS. The SDK works on 5.1, but newer modules and performance characteristics assume 7.x.
Conclusion
These ten patterns cover the chores that quietly consume admin hours every month. Start with the stale-device report — it delivers visible value in week one — then add the assignment audit before your next change board. Every script here uses the same three Graph primitives you already know: GET with paging, POST actions with pacing, and CSV evidence at the end.
Comments
0No comments yet — be the first to share your thoughts.