If your PowerShell scripts still Import-Module MSOnline or AzureAD, they’re already legacy. And if you just thought “That’s not me,” this episode is exactly for you. The cloud moved on. Your modules didn’t. They break on Linux runners, containers, CI/CD. REST doesn’t. PowerShell isn’t dead — but the “old module era” is. We’re going API-first with Microsoft Graph, and in this session you’ll see the pattern and walk away with scripts that run anywhere. You’ll learn:
- The API-first, module-free PowerShell pattern for Microsoft Graph
- Three auth flows (device code, cert-based, Managed Identity) and when to use each
- Three enterprise demos: Intune cleanup, identity onboarding, and compliance drift remediation
- The stupidly simple Graph gotcha that breaks most scripts (and how to never hit it again)
If you’re still loading legacy modules in 2025, you’re basically heating the office with old Exchange servers. 🔥 Why PowerShell Without Modules Is the Future We start by ripping off the band-aid:
- Everything that matters is in Microsoft Graph now
- Users, groups, devices, Intune, Teams, SharePoint, licenses, app regs, and more
- The portal rides Graph. Your scripts should too.
- REST beats modules in 2025:
- No module load times
- No dependency roulette or weird version drift
- No “works on my laptop” when your CI/CD runner is Linux
- Tokens beat credentials. Full stop.
- OAuth2 + certificates or Managed Identity
- Short-lived tokens, clean audit trails
- No passwords in scripts, no sticky notes, no “who owns this account?” drama
- Cloud-native, cross-platform reality:
- PowerShell Core works on Windows, Linux, containers, GitHub Actions, Functions
- Graph is the constant behind all of them
- Invoke-RestMethod + Graph works everywhere
- Why security and leadership like this:
- Least-privilege scopes per job / app registration
- Admin consent reviewed on schedule
- Every call has request IDs & correlation IDs for audits
- You don’t depend on a third-party module maintainer’s calendar
Key idea:
Modules lag. Graph is always first. If a feature exists, it lands in Graph before it shows up in a PowerShell module—if it ever does. 🧩 The Core Pattern: PowerShell + REST + Graph We walk through the one pattern you’ll reuse for everything: Token → Headers → REST call → Paging/Retry → Done 1. Get a Token (Three Real-World Flows)
- Device Code (local dev & testing)
- Great for: interactive console dev
- Flow: request code → browser prompt → token for https://graph.microsoft.com with scopes
- Not for production (humans are flaky)
- Client Credentials + Certificate (headless automation)
- For: CI/CD, scheduled jobs, back-end services
- App registration with only required Graph app roles
- JWT signed with cert → token for https://graph.microsoft.com/.default
- No client secrets in scripts. Ever.
- Managed Identity (cloud-native, best option)
- For: Azure Automation, Functions, VMs, containers in Azure
- Call local identity endpoint → get Graph token
- No secrets. No Key Vault lookups in the script. Just proper RBAC.
2. Build the Headers Simple but critical:
- Authorization: Bearer
- Content-Type: application/json for requests with bodies
- ConsistencyLevel: eventual + Prefer: … when needed (advanced queries, $count, search)
Most people miss ConsistencyLevel and then wonder why advanced queries feel drunk. 3. Call Graph with Invoke-RestMethod
- Invoke-RestMethod -Method GET/POST/PATCH/DELETE -Uri $uri -Headers $headers -Body ($body | ConvertTo-Json)
- You handle:
- Paging via @odata.nextLink
- Throttling via status 429/503 and Retry-After
- Retries with exponential backoff + jitter
Common failure modes we call out:
- Wrong audience (token for management.azure.com but calling Graph 👉 401 all day)
- Ignoring @odata.nextLink and thinking Graph “only returns 100 rows”
- Hammering endpoints with tight loops and no delay → throttle hell
- Granting Directory.ReadWrite.All “just to test” and failing your audit in advance
Pro tip:
Write one retry helper and one pagination helper and reuse them everywhere. Your scripts shrink. Your failure rate drops. 🛠 Enterprise Demo 1 — Intune Device Cleanup (No Modules) Problem:
Intune tenants rot. Ghost devices. Old laptops. Duplicate entries. Reports lie. Compliance looks better than reality. Goal:
Use Graph-only PowerShell to find, classify, and clean stale devices—safely and on a schedule. What we do:
- Query Intune devices via Graph:
- GET https://graph.microsoft.com/beta/deviceManagement/managedDevices
- Use $select to trim payload (e.g., id,deviceName,operatingSystem,lastCheckInDateTime,managedDeviceOwnerType,azureADDeviceId)
- Apply server-side $filter where possible (e.g., stale lastCheckInDateTime)
- Follow paging until no @odata.nextLink remains
- Classify devices by:
- Age thresholds (e.g., 30/60/120+ days)
- Ownership (corporate vs personal)
- Tags for exclusions (lab, loaner, break-glass)
- Take actions via Graph:
- Retire: POST /managedDevices/{id}/retire
- Delete: DELETE /managedDevices/{id}
- “Disable” via tags or policy triggers, if applicable
- All actions log to Log Analytics with runId, deviceId, action, reason, result
- Automation setup:
- Azure Automation with Managed Identity
- Graph app roles: only what’s needed for read + device actions
- Non-secret config in variables (thresholds, tags, dry-run flag)
We explicitly stress:
- Dry-run mode first (log-only)
- Respect Retry-After and throttling
- Never run this with god-mode Directory.ReadWrite.All “just to get started.”
Become a supporter of this podcast: https://www.spreaker.com/podcast/m365-show-podcast–6704921/support.
Follow us on:
LInkedIn
Substack
Source link