Dec. 8, 2025

No Modules. No Dependencies. No Limits: PowerShell + Graph API the Modern Way

Still writing PowerShell against MSOnline and AzureAD modules in 2025? This episode explains why that stack is legacy – and how to go API-first with pure REST and Microsoft Graph. We walk through the core “token, headers, REST call” pattern, three real-world auth flows (device code, client credentials with certificates, and managed identity), plus the one token audience gotcha that breaks most Graph scripts.

You’ll see how to build cross-platform Graph automation that runs cleanly on Linux, containers, GitHub Actions, Azure Functions, and Azure Automation – with no fragile module dependencies. Then we apply the pattern to enterprise scenarios: Intune device cleanup, identity onboarding, and compliance drift detection and remediation, all with least-privilege Graph permissions, robust retry logic, pagination helpers, and full audit trails in Log Analytics.

If you’re an Azure, Intune, or Microsoft 365 engineer who’s tired of “works on my laptop” modules, this practical Graph-first PowerShell deep dive is your fast path off legacy cmdlets.

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

Transcript

1
00:00:00,000 --> 00:00:03,880
If your script is still lean on MS Online or Azure AD, they're already legacy.

2
00:00:03,880 --> 00:00:05,800
And if you think that doesn't apply to you,

3
00:00:05,800 --> 00:00:08,080
oh boy, you're exactly who I'm talking to.

4
00:00:08,080 --> 00:00:09,080
The cloud moved on.

5
00:00:09,080 --> 00:00:10,040
Your modules didn't.

6
00:00:10,040 --> 00:00:12,640
Modules break on Linux runners, containers, CI/CD.

7
00:00:12,640 --> 00:00:13,440
Rest doesn't.

8
00:00:13,440 --> 00:00:14,640
PowerShell isn't going away.

9
00:00:14,640 --> 00:00:16,640
The modules are, we're going API first.

10
00:00:16,640 --> 00:00:18,840
I'll show you the raw rest pattern, three auth flows,

11
00:00:18,840 --> 00:00:21,000
and three enterprise demos that actually ship.

12
00:00:21,000 --> 00:00:24,480
There's one gotcha that ruins most graph scripts will fix it later.

13
00:00:24,480 --> 00:00:26,880
If you're still loading modules in 2025,

14
00:00:26,880 --> 00:00:29,680
you're heating the office with old exchange servers.

15
00:00:29,680 --> 00:00:32,560
Why PowerShell without modules is the future?

16
00:00:32,560 --> 00:00:35,400
Everything you care about lives in Microsoft Graph now.

17
00:00:35,400 --> 00:00:39,480
Users, groups, devices, Intune Teams, SharePoint licenses, app registrations,

18
00:00:39,480 --> 00:00:41,800
the portal writes graph, your scripts should too.

19
00:00:41,800 --> 00:00:44,440
Rest beats modules because it cuts out the middle mess,

20
00:00:44,440 --> 00:00:47,040
no load times, no dependency, roulette, no version drama.

21
00:00:47,040 --> 00:00:50,040
You call the endpoint, you get the data, you move on with your day.

22
00:00:50,040 --> 00:00:51,920
Token speed credentials full stop.

23
00:00:51,920 --> 00:00:55,040
Oauth2 with search or managed identity gives you short-lived access,

24
00:00:55,040 --> 00:00:58,280
clean audit trails, and automation that doesn't depend on a human

25
00:00:58,280 --> 00:01:01,040
remembering a password they already wrote on a sticky note.

26
00:01:01,040 --> 00:01:03,000
Managed identity means no secrets at all.

27
00:01:03,000 --> 00:01:04,000
That's the point.

28
00:01:04,000 --> 00:01:06,880
Less to steal, less to rotate, less to screw up.

29
00:01:06,880 --> 00:01:09,080
CloudNative means it runs everywhere.

30
00:01:09,080 --> 00:01:15,080
Azure Automation, Functions, GitHub Actions, Containers, Linux, Local,

31
00:01:15,080 --> 00:01:18,040
PowerShell Core is cross-platform, but Graph is the constant.

32
00:01:18,040 --> 00:01:19,840
Curl works on anything with a pulse.

33
00:01:19,840 --> 00:01:22,920
Invogrest method does the job without dragging in the structural integrity

34
00:01:22,920 --> 00:01:24,000
of wet cardboard.

35
00:01:24,000 --> 00:01:27,360
Remember when installing a module meant praying to the new get-gods?

36
00:01:27,360 --> 00:01:28,840
Those days are over.

37
00:01:28,840 --> 00:01:31,200
Benchmarks aren't glamorous, but they're loud.

38
00:01:31,200 --> 00:01:34,560
Load time, modules lag, rest is fire and go.

39
00:01:34,560 --> 00:01:36,960
Cold start in a function or an actions runner.

40
00:01:36,960 --> 00:01:38,400
Rest starts immediately.

41
00:01:38,400 --> 00:01:40,200
Modules sit there thinking.

42
00:01:40,200 --> 00:01:44,680
Reliability, modules choke on throttling or stale tokens you never ask for.

43
00:01:44,680 --> 00:01:48,320
Rest is predictable if you set headers and handle retries.

44
00:01:48,320 --> 00:01:52,320
Portability, Linux and containers don't care about your module drama.

45
00:01:52,320 --> 00:01:54,000
Rest just runs.

46
00:01:54,000 --> 00:01:56,840
Here's the business side because someone will ask about value.

47
00:01:56,840 --> 00:02:00,000
Faster delivery because you don't wait on module updates.

48
00:02:00,000 --> 00:02:03,000
Fewer outages because you control the token and the retry logic.

49
00:02:03,000 --> 00:02:06,280
Easier governance because permissions are explicit and scoped per job,

50
00:02:06,280 --> 00:02:08,400
not hidden inside someone's global profile.

51
00:02:08,400 --> 00:02:11,920
Cost, your cold start, stop wasting minutes, your failures go down.

52
00:02:11,920 --> 00:02:13,720
No works on my laptop nonsense.

53
00:02:13,720 --> 00:02:16,400
The thing most people miss, Graph updates instantly,

54
00:02:16,400 --> 00:02:18,520
modules lag by weeks or never.

55
00:02:18,520 --> 00:02:20,280
New feature? It lands on Graph first.

56
00:02:20,280 --> 00:02:21,720
You can call it today.

57
00:02:21,720 --> 00:02:25,040
Waiting for a module means waiting for a maintainer who isn't on your payroll.

58
00:02:25,040 --> 00:02:26,920
Meanwhile, your project deadline didn't move.

59
00:02:26,920 --> 00:02:30,360
And yes, modules load slower than my ancient exchange server.

60
00:02:30,360 --> 00:02:32,240
Graph doesn't care. It just responds.

61
00:02:32,240 --> 00:02:34,280
You can pin versions on V1.

62
00:02:34,280 --> 00:02:36,880
Test beta endpoints when needed and guarded with feature flags.

63
00:02:36,880 --> 00:02:39,200
You get to control change instead of being surprised by it.

64
00:02:39,200 --> 00:02:41,040
Security teams will actually like this.

65
00:02:41,040 --> 00:02:43,120
These privileged scopes per app registration.

66
00:02:43,120 --> 00:02:44,760
Admin consent reviewed on a schedule.

67
00:02:44,760 --> 00:02:46,800
Search based auth with short lifetimes.

68
00:02:46,800 --> 00:02:48,720
Managed identity where you can.

69
00:02:48,720 --> 00:02:50,560
Search where you must.

70
00:02:50,560 --> 00:02:51,760
Every call leaves a trail.

71
00:02:51,760 --> 00:02:54,840
Request IDs, correlation IDs, who consented to what?

72
00:02:54,840 --> 00:02:57,560
You don't get that from a plain text password stuffed into a script

73
00:02:57,560 --> 00:02:59,560
like a loose wire in a breaker panel.

74
00:02:59,560 --> 00:03:02,360
So why now? Because the cross-platform reality is here.

75
00:03:02,360 --> 00:03:04,360
You're running on Linux, runners, building containers,

76
00:03:04,360 --> 00:03:05,880
pushing jobs into functions.

77
00:03:05,880 --> 00:03:08,280
The module stack was built for a Windows First World

78
00:03:08,280 --> 00:03:09,640
and a simpler set of products.

79
00:03:09,640 --> 00:03:11,080
We don't live there anymore.

80
00:03:11,080 --> 00:03:12,080
All right?

81
00:03:12,080 --> 00:03:13,120
Enough theory.

82
00:03:13,120 --> 00:03:14,600
Here's the pattern you'll use everywhere.

83
00:03:14,600 --> 00:03:17,240
Get a token, set headers, call rest, handle paging,

84
00:03:17,240 --> 00:03:18,800
honor retry after and move on.

85
00:03:18,800 --> 00:03:21,000
It's boring, which is why it works.

86
00:03:21,000 --> 00:03:21,880
The core pattern.

87
00:03:21,880 --> 00:03:24,840
Native PowerShell plus rest plus graph API.

88
00:03:24,840 --> 00:03:26,040
Here's the pattern I promised.

89
00:03:26,040 --> 00:03:27,560
Token, headers, rest, call.

90
00:03:27,560 --> 00:03:28,440
That's the loop.

91
00:03:28,440 --> 00:03:30,400
You'll reuse it for everything from listing users

92
00:03:30,400 --> 00:03:32,480
to smacking non-compliant devices.

93
00:03:32,480 --> 00:03:34,320
Scripts don't fail because of PowerShell.

94
00:03:34,320 --> 00:03:35,560
They fail because of assumptions.

95
00:03:35,560 --> 00:03:36,880
So stop assuming magic.

96
00:03:36,880 --> 00:03:39,400
Build the three pieces every time and you'll sleep at night.

97
00:03:39,400 --> 00:03:40,640
Start with the token.

98
00:03:40,640 --> 00:03:43,400
You've got three ways to get one and they map to real life.

99
00:03:43,400 --> 00:03:46,520
Device code for local testing when it's just you at a console.

100
00:03:46,520 --> 00:03:48,760
Client credentials with a certificate for automation

101
00:03:48,760 --> 00:03:50,360
where no one's clicking anything.

102
00:03:50,360 --> 00:03:52,240
Managed identity when you're in Azure

103
00:03:52,240 --> 00:03:54,960
and you want secrets to disappear like they should have years ago.

104
00:03:54,960 --> 00:03:56,440
Same outcome, different doors.

105
00:03:56,440 --> 00:03:57,800
Device code is the friendly one.

106
00:03:57,800 --> 00:04:01,840
You request a token for HTTPS, graph, Microsoft.com.

107
00:04:01,840 --> 00:04:05,160
With the scopes you need, you get a code, you open a browser,

108
00:04:05,160 --> 00:04:08,080
you confirm it's you and PowerShell gets a token back.

109
00:04:08,080 --> 00:04:10,640
Great for building the first version and poking endpoints.

110
00:04:10,640 --> 00:04:13,480
Bad for production because humans are squishy and forgetful.

111
00:04:13,480 --> 00:04:15,080
Client credentials is the adult path.

112
00:04:15,080 --> 00:04:16,400
You create an app registration.

113
00:04:16,400 --> 00:04:18,880
You granted only the graph application permissions it needs

114
00:04:18,880 --> 00:04:20,160
and you add a certificate.

115
00:04:20,160 --> 00:04:22,480
Your script signs a JWT with that cert

116
00:04:22,480 --> 00:04:25,800
and requests a token using the pass-sass default scope for graph.

117
00:04:25,800 --> 00:04:27,200
No user, no prompts.

118
00:04:27,200 --> 00:04:29,320
Clean audit trail, rotate the cert and move on.

119
00:04:29,320 --> 00:04:31,520
If I see a client secret pasted in plain text again,

120
00:04:31,520 --> 00:04:32,680
I'm revoking Wi-Fi.

121
00:04:32,680 --> 00:04:34,600
Managed identity is the quiet killer.

122
00:04:34,600 --> 00:04:37,840
You enable it on your automation account, function app or VM.

123
00:04:37,840 --> 00:04:40,880
Then you call the local identity endpoint, ask for a graph token

124
00:04:40,880 --> 00:04:43,120
and Azure hands you one tied to that identity.

125
00:04:43,120 --> 00:04:44,560
No vault lookups in your script.

126
00:04:44,560 --> 00:04:45,960
No secrets to rotate.

127
00:04:45,960 --> 00:04:47,640
You just need to grant that identity.

128
00:04:47,640 --> 00:04:52,160
The graph app rolls it requires at least privilege means fewer to a m calls.

129
00:04:52,160 --> 00:04:53,160
Now the headers.

130
00:04:53,160 --> 00:04:54,240
Don't overthink it.

131
00:04:54,240 --> 00:04:55,960
Authorization, bearer your token.

132
00:04:55,960 --> 00:04:58,880
Content type, application, JSON for anything with a body.

133
00:04:58,880 --> 00:05:02,680
When you're doing advanced queries or searches, add consistency.

134
00:05:02,680 --> 00:05:05,680
Level, eventual and the appropriate prefer headers

135
00:05:05,680 --> 00:05:07,480
if the endpoint supports them.

136
00:05:07,480 --> 00:05:10,120
The thing most people miss is they forget the consistency level

137
00:05:10,120 --> 00:05:13,000
and then wonder why their account or filter looks drunk.

138
00:05:13,000 --> 00:05:14,240
Then make the call.

139
00:05:14,240 --> 00:05:15,680
In VogueGress method is fine.

140
00:05:15,680 --> 00:05:17,280
Method, URI,

141
00:05:17,280 --> 00:05:19,280
headers, maybe a body.

142
00:05:19,280 --> 00:05:20,600
The mental model is simple.

143
00:05:20,600 --> 00:05:24,040
Token, headers, call, check, page, retry, continue.

144
00:05:24,040 --> 00:05:26,080
You'll page through results using AdO data.

145
00:05:26,080 --> 00:05:27,440
Next link whenever it shows up.

146
00:05:27,440 --> 00:05:29,520
If you only got 100 items, that's not a mystery.

147
00:05:29,520 --> 00:05:31,080
That's the default page size.

148
00:05:31,080 --> 00:05:32,880
Follow next link until it stops.

149
00:05:32,880 --> 00:05:36,120
Put a guard on your loop so it can't run forever if the API burps.

150
00:05:36,120 --> 00:05:37,560
Now here's where most people mess up.

151
00:05:37,560 --> 00:05:39,240
You must respect throttling.

152
00:05:39,240 --> 00:05:43,480
Graph doesn't care about your feelings, implement retries or enjoy failures.

153
00:05:43,480 --> 00:05:46,680
If you see 420503 look for retry after,

154
00:05:46,680 --> 00:05:48,680
sleep for that duration plus a little jitter

155
00:05:48,680 --> 00:05:51,640
so you don't join a thundering herd, then try again.

156
00:05:51,640 --> 00:05:53,840
Exponential back off beats panic clicking.

157
00:05:53,840 --> 00:05:57,160
If your automation can't survive transient errors, it's not automation.

158
00:05:57,160 --> 00:05:58,360
It's a suggestion.

159
00:05:58,360 --> 00:06:00,520
Common mistakes, so you don't repeat them.

160
00:06:00,520 --> 00:06:03,600
One wrong audience, you ask entra for a token to management.

161
00:06:03,600 --> 00:06:06,440
Azure.com and then called graph, Microsoft.com.

162
00:06:06,440 --> 00:06:08,560
That's a 401, not a conspiracy.

163
00:06:08,560 --> 00:06:11,440
Two pagination denial, why 100 rows only?

164
00:06:11,440 --> 00:06:13,000
Because you never read next link?

165
00:06:13,000 --> 00:06:15,040
Three tight loops without delay.

166
00:06:15,040 --> 00:06:17,760
You angered the throttle gods and now everything slower.

167
00:06:17,760 --> 00:06:20,120
Four over permissioned app with directory.

168
00:06:20,120 --> 00:06:21,360
Read right all just to test.

169
00:06:21,360 --> 00:06:23,280
You just failed and ordered you haven't had yet.

170
00:06:23,280 --> 00:06:25,640
Let me show you the quick wins you can do today.

171
00:06:25,640 --> 00:06:29,480
Device code, grab a token, get me and confirm you can read your own profile.

172
00:06:29,480 --> 00:06:31,480
That proves your token and headers are wired.

173
00:06:31,480 --> 00:06:34,360
Client credentials use default call users,

174
00:06:34,360 --> 00:06:39,240
select id, display name, mail to keep payload small and process a page or two.

175
00:06:39,240 --> 00:06:40,360
Managed identity.

176
00:06:40,360 --> 00:06:45,840
In Azure call intune's device endpoint via graph, set top, follow next link and dump only

177
00:06:45,840 --> 00:06:48,760
id device name and last check in date time.

178
00:06:48,760 --> 00:06:51,160
Good words, bad scripts because contrast helps.

179
00:06:51,160 --> 00:06:54,840
Bad module error, import, fail update, fail, copy, fail.

180
00:06:54,840 --> 00:06:57,080
Good rest error token call done.

181
00:06:57,080 --> 00:07:01,480
Sure, your rapid and functions are at logging, but the backbone is boring on purpose.

182
00:07:01,480 --> 00:07:06,000
One more pro move, build a tiny retry helper and a pagination helper once.

183
00:07:06,000 --> 00:07:09,920
Pass in the your eye and headers, get back the full data set with retries already handled.

184
00:07:09,920 --> 00:07:13,320
Suddenly every script is 20 lines shorter and 10 times calmer.

185
00:07:13,320 --> 00:07:17,680
The game changer nobody talks about is you can test these helpers locally, then drop them

186
00:07:17,680 --> 00:07:20,320
in a container or a function without changing a line.

187
00:07:20,320 --> 00:07:23,520
Done, enterprise demo one, intune device cleanup.

188
00:07:23,520 --> 00:07:27,520
Ten and SWAT devices stack up like abandoned cards in a grocery lot.

189
00:07:27,520 --> 00:07:31,600
Policies get noisy, compliance drifts and suddenly your reports look haunted.

190
00:07:31,600 --> 00:07:35,680
Let's clean it with graph, no modules on a schedule, with logs you can show to security

191
00:07:35,680 --> 00:07:36,680
without blushing.

192
00:07:36,680 --> 00:07:41,040
Here's the plan, we query intune devices from graph where last check and date time is older

193
00:07:41,040 --> 00:07:42,920
than a threshold you set.

194
00:07:42,920 --> 00:07:48,120
We decide action by agent tags, disable if stale, retire if older, delete if fossilized.

195
00:07:48,120 --> 00:07:51,440
And we check ownership first so you don't nuke personal devices because someone missed

196
00:07:51,440 --> 00:07:54,840
a field, boring, predictable, safe.

197
00:07:54,840 --> 00:07:59,160
Start with the end point, you're calling gethttps/graph.

198
00:07:59,160 --> 00:08:03,600
Microsoft.com/beta-divisemanagement-devices-with-select-trim-payload.

199
00:08:03,600 --> 00:08:09,760
ID, device name, operating system, last check and date time, manage device owner type,

200
00:08:09,760 --> 00:08:13,160
as your AD device eat and any tag you rely on.

201
00:08:13,160 --> 00:08:14,960
Use filter for server side cut.

202
00:08:14,960 --> 00:08:20,280
Last check and date time LT24-0101-TZ-UZC-OCE.

203
00:08:20,280 --> 00:08:23,760
If you can't filter exactly how you want, pull with the conservative window and filter

204
00:08:23,760 --> 00:08:24,760
in PowerShell.

205
00:08:24,760 --> 00:08:25,760
You'll get paging.

206
00:08:25,760 --> 00:08:27,880
Follow @odeta.nextlink until it stops.

207
00:08:27,880 --> 00:08:29,800
Guard the loop so it can't spin forever.

208
00:08:29,800 --> 00:08:34,240
Then classification, corporate owned, evaluate action thresholds.

209
00:08:34,240 --> 00:08:42,080
For example, 30 to 60 days, mark for review, 60 to 120, retire, 120 plus delete.

210
00:08:42,080 --> 00:08:45,360
Personal owned, maybe you only notify or tag for review.

211
00:08:45,360 --> 00:08:50,240
The thing most people miss is time skew and inactive but just reprovision devices.

212
00:08:50,240 --> 00:08:54,560
Cross check as your AD device ID against Entra device last seen if you need more confidence.

213
00:08:54,560 --> 00:09:00,480
If there's conflict, skip and lock now actions retire is opposed to manage devices, ID retire.

214
00:09:00,480 --> 00:09:04,520
Delete is delete, manage devices like ID.

215
00:09:04,520 --> 00:09:08,320
Disable often means flipping state where supported or writing a tag and letting policy handle

216
00:09:08,320 --> 00:09:11,720
it, batch where the endpoint supports it but don't stamp it.

217
00:09:11,720 --> 00:09:15,720
Respect for 29503, owner, retry after with jitter.

218
00:09:15,720 --> 00:09:20,680
Write every action to lock analytics, device, ID, action, recent time stamp result, request

219
00:09:20,680 --> 00:09:24,800
ID, correlate with a runead so you can reconstruct the story later.

220
00:09:24,800 --> 00:09:25,800
Horror time.

221
00:09:25,800 --> 00:09:30,000
I watched someone delete 800 devices because they didn't understand last check in timestamps.

222
00:09:30,000 --> 00:09:34,320
They filtered on a property that lagged for re-enrolled devices and skipped dry run.

223
00:09:34,320 --> 00:09:38,280
Graphed it exactly what they asked, it always does, in tune, never lies but boy does it stay

224
00:09:38,280 --> 00:09:41,120
quiet until it's too late, don't be that headline.

225
00:09:41,120 --> 00:09:46,040
Automation setup, use an automation account with a system assigned managed identity, granted

226
00:09:46,040 --> 00:09:50,360
least privilege, graph roles for device read and the specific device actions.

227
00:09:50,360 --> 00:09:55,080
For non-secret config invariables, thresholds, tag names, action map have a feature flag

228
00:09:55,080 --> 00:09:56,080
for dry run.

229
00:09:56,080 --> 00:09:58,040
Dry run writes what it would do not what it did.

230
00:09:58,040 --> 00:10:01,240
Run that first, then run it again, then maybe touch production.

231
00:10:01,240 --> 00:10:05,220
Mistakes to avoid, looking devices during a regional time skew, forgetting to limit by

232
00:10:05,220 --> 00:10:07,880
platform when your Mac fleet reports differently.

233
00:10:07,880 --> 00:10:11,600
Running with directory, read write, all just to test.

234
00:10:11,600 --> 00:10:16,120
No back off policy and hitting global throttle so the next team's job also fails.

235
00:10:16,120 --> 00:10:18,240
Write locks, not feelings, punch line.

236
00:10:18,240 --> 00:10:22,840
If the portal shows it, graph can do it faster, quieter and on schedule.

237
00:10:22,840 --> 00:10:25,920
No module drama, just tokens headers calls.

238
00:10:25,920 --> 00:10:30,680
Enterprise demo 2, identity onboarding via graph only, 450 words.

239
00:10:30,680 --> 00:10:33,360
Onboarding should be boring, if it's exciting something is wrong.

240
00:10:33,360 --> 00:10:38,560
We're wiring HR to identity with graph, so accounts show up, licensed, grouped and ready,

241
00:10:38,560 --> 00:10:41,000
before the manager gets impatient and opens a ticket.

242
00:10:41,000 --> 00:10:44,440
Flow is simple, client credentials with a certificate, not a secret.

243
00:10:44,440 --> 00:10:47,320
Your app registration has only the graph app roles it needs.

244
00:10:47,320 --> 00:10:51,040
User, read write, all if you must create users group.

245
00:10:51,040 --> 00:10:55,400
Read write, all if you must assign membership, directory, read all for lookups and the license

246
00:10:55,400 --> 00:10:56,640
assignment roles.

247
00:10:56,640 --> 00:10:58,880
Admin consented once, reviewed quarterly.

248
00:10:58,880 --> 00:11:05,360
Your script signs the request, asks graph for a token using passgars default for http.graph.microsoft.com

249
00:11:05,360 --> 00:11:06,840
and starts the pipeline.

250
00:11:06,840 --> 00:11:08,800
Step one, create the user.

251
00:11:08,800 --> 00:11:11,080
Post 2, users with minimal attributes.

252
00:11:11,080 --> 00:11:16,240
Account enabled, true, display name, mail nickname, user principle name, usage location

253
00:11:16,240 --> 00:11:20,000
and a temporary password with force change password next sign.

254
00:11:20,000 --> 00:11:23,400
In true, if you're not using SSPR start, keep it lean.

255
00:11:23,400 --> 00:11:25,840
If the user already exists, you patch, not freak out.

256
00:11:25,840 --> 00:11:29,800
It impotency means you can rerun safely after a failure and it won't make a mess.

257
00:11:29,800 --> 00:11:31,840
Step 2, assign a baseline license.

258
00:11:31,840 --> 00:11:36,160
You'll get subscribescuse once, cache the skew map and pick the right skew ID, then post

259
00:11:36,160 --> 00:11:41,320
2, users rush ID, assign license with ad licenses containing the skew ID and disable plans

260
00:11:41,320 --> 00:11:45,080
array if you do selective services, handle quota gracefully.

261
00:11:45,080 --> 00:11:48,880
If you're out of licenses, you log a blocking event and notify the right channel, not

262
00:11:48,880 --> 00:11:51,440
explode the run and leave half created objects.

263
00:11:51,440 --> 00:11:53,200
Step 3, groups by role.

264
00:11:53,200 --> 00:11:57,520
You keep a configuration map from job code or department to static group IDs.

265
00:11:57,520 --> 00:11:58,760
Names drift IDs don't.

266
00:11:58,760 --> 00:12:05,200
You put or post 2, groups slash, group id, members ref with the user's directory object ID.

267
00:12:05,200 --> 00:12:07,200
If the user is already a member, skip.

268
00:12:07,200 --> 00:12:10,920
If the group doesn't exist, that's a configuration failure, not a runtime adventure.

269
00:12:10,920 --> 00:12:15,840
For script keeps moving for other memberships and logs they miss with correlation id.

270
00:12:15,840 --> 00:12:17,680
Step 4, app access.

271
00:12:17,680 --> 00:12:20,840
Many enterprise apps hang off group assignments or app roles.

272
00:12:20,840 --> 00:12:26,240
For app roles, you post to a service principles, a speed, app role assigned to with the user's

273
00:12:26,240 --> 00:12:28,640
object id and the app role id.

274
00:12:28,640 --> 00:12:32,160
For group based SSO, adding the user to the right group is enough.

275
00:12:32,160 --> 00:12:33,880
Again, use IDs from config.

276
00:12:33,880 --> 00:12:38,040
No name, lookups and hotpots, guard rails, correlation id per onboarding.

277
00:12:38,040 --> 00:12:42,720
Every graph call logs request id, URI, method, status, duration, retries.

278
00:12:42,720 --> 00:12:49,160
Retry, back off on 429.5.6, feature flag for dry run, which creates a plan, but does no rights.

279
00:12:49,160 --> 00:12:50,600
Item potency everywhere.

280
00:12:50,600 --> 00:12:54,800
If user exists, patch, if license exists, skip.

281
00:12:54,800 --> 00:12:57,080
If group membership exists, skip.

282
00:12:57,080 --> 00:13:01,240
And for the last time, if I see a client secret hard coded again, I'm revoking Wi-Fi.

283
00:13:01,240 --> 00:13:02,720
User third or manage identity.

284
00:13:02,720 --> 00:13:05,440
Quick win, this runs on a Linux runner with PowerShell Core.

285
00:13:05,440 --> 00:13:08,760
No modular load, no waiting for someone to publish a fix.

286
00:13:08,760 --> 00:13:13,040
User shows up in seconds with baseline access and your help desk doesn't touch a thing.

287
00:13:13,040 --> 00:13:17,240
Now your pipeline is the quiet boring part of onboarding, the way it should be.

288
00:13:17,240 --> 00:13:20,720
Enterprise demo three, compliance drift detection and remediation.

289
00:13:20,720 --> 00:13:23,440
Compliance sprawl is the slow leak that flattens your weak.

290
00:13:23,440 --> 00:13:27,120
Devices drift, users get risky, tickets pile up like snow.

291
00:13:27,120 --> 00:13:31,720
We're going to scan, target, remediate and verify, all with graph, no modules, and without

292
00:13:31,720 --> 00:13:35,880
waking up set-ups at 2 a.m. start with a schedule and a managed identity.

293
00:13:35,880 --> 00:13:38,560
This job isn't special, it just needs to be reliable.

294
00:13:38,560 --> 00:13:41,920
The identity gets only the graph rolls it needs.

295
00:13:41,920 --> 00:13:45,800
Device compliance read, device actions if you remediate identity protection, read for

296
00:13:45,800 --> 00:13:49,000
user risk and the session revoke permission.

297
00:13:49,000 --> 00:13:53,480
Don't grab directory.

298
00:13:53,480 --> 00:13:54,480
Read right.

299
00:13:54,480 --> 00:13:56,960
All just to test.

300
00:13:56,960 --> 00:13:58,640
That's how audits become folklore.

301
00:13:58,640 --> 00:14:05,920
First pass devices call get a tbsgrushishgraph.microsoft.com/v1, device management, device compliance policy,

302
00:14:05,920 --> 00:14:10,640
settings eight, summaries or the device compliance states and point your tenant users.

303
00:14:10,640 --> 00:14:12,560
Use select to keep payload small.

304
00:14:12,560 --> 00:14:17,240
ID, device name, user principle name, operating system compliance state.

305
00:14:17,240 --> 00:14:19,840
Filter where you can, compliance state EQ non-compliant.

306
00:14:19,840 --> 00:14:24,320
You'll get pages, follow adodata.next link, put a guard on the loop, now classify.

307
00:14:24,320 --> 00:14:27,280
Non-compliant doesn't mean execute order 66.

308
00:14:27,280 --> 00:14:31,520
You map severity to action for low severity as maybe a push notification or an email with

309
00:14:31,520 --> 00:14:36,200
a remediation guide, medium trigger a remediation script or force of policy sync.

310
00:14:36,200 --> 00:14:39,400
High, quarantine the device or block access to sensitive apps.

311
00:14:39,400 --> 00:14:44,040
Batch where the endpoint supports it, but keep the degree of parallelism low.

312
00:14:44,040 --> 00:14:46,320
Throttling friendly, not stumpy.

313
00:14:46,320 --> 00:14:48,160
When you act, log like an adult.

314
00:14:48,160 --> 00:14:52,520
For every device you touch, write run ID, device it action reason, request it, status and

315
00:14:52,520 --> 00:14:54,000
latency to log analytics.

316
00:14:54,000 --> 00:14:57,880
If a device flips to compliant during the run, skip and note the flip.

317
00:14:57,880 --> 00:14:59,960
After a remediation, reach agst status.

318
00:14:59,960 --> 00:15:04,680
If it's still non-compliant, escalate once, not five times, alert thresholds, not spam,

319
00:15:04,680 --> 00:15:05,680
users next.

320
00:15:05,680 --> 00:15:08,000
Pull risky users from identity protection via get.

321
00:15:08,000 --> 00:15:15,480
HTTPS, xxgraph, Microsoft.com/v1, identity protection, risky users, filter, risk level

322
00:15:15,480 --> 00:15:20,760
EQ high and selected user principle name risk level risk state for each high risk user

323
00:15:20,760 --> 00:15:27,200
take targeted action, revoke sign in sessions via posts or users to ID, revoke sign in sessions.

324
00:15:27,200 --> 00:15:32,400
If your policy demands it temporarily block sign in, patch users ID with account enabled

325
00:15:32,400 --> 00:15:36,840
false time box and logged mini rant, don't carpet bomb sign in, use severity.

326
00:15:36,840 --> 00:15:39,800
You're not diffusing a movie bomb.

327
00:15:39,800 --> 00:15:42,160
Reality check compliance isn't a state.

328
00:15:42,160 --> 00:15:44,040
It's a drifting target you have to chase.

329
00:15:44,040 --> 00:15:46,160
That's why we use delta where possible.

330
00:15:46,160 --> 00:15:51,240
For device compliance, if delta endpoints exist for your scenario, use them to avoid rescanning

331
00:15:51,240 --> 00:15:52,240
the world.

332
00:15:52,240 --> 00:15:58,680
For users, keep a cache of last process risk change timestamp query only what changed since.

333
00:15:58,680 --> 00:16:01,400
That's how you keep runs under budget and under the throttle radar.

334
00:16:01,400 --> 00:16:05,400
Common mistakes, blanket blocks without a severity filter, congratulations, you just created

335
00:16:05,400 --> 00:16:06,840
a help desk fire drill.

336
00:16:06,840 --> 00:16:08,040
No audit lock.

337
00:16:08,040 --> 00:16:11,320
Now security wants names, times and reasons you can't show.

338
00:16:11,320 --> 00:16:14,680
Hard coded IDs, someone renames a policy and your script face plans.

339
00:16:14,680 --> 00:16:16,600
Identity IDs in config, not code.

340
00:16:16,600 --> 00:16:19,640
If you can't reproduce a run from logs, you're guessing.

341
00:16:19,640 --> 00:16:26,560
Punch line, detect target, remediate, verify, log, quiet, repeatable and boring on purpose.

342
00:16:26,560 --> 00:16:31,120
Architecture breakdown, identity, automation, execution, observability, you've seen the

343
00:16:31,120 --> 00:16:32,120
pattern.

344
00:16:32,120 --> 00:16:33,120
Now why are the plumbing?

345
00:16:33,120 --> 00:16:35,760
So it doesn't fall over when someone sneezes near Azure.

346
00:16:35,760 --> 00:16:37,120
Identity layer first.

347
00:16:37,120 --> 00:16:39,440
Managed identity wherever the workload lives.

348
00:16:39,440 --> 00:16:43,360
Automation account, function app container in ACI, VM, flip it on.

349
00:16:43,360 --> 00:16:47,520
And only the graph app rolls the job needs and stop thinking about secrets.

350
00:16:47,520 --> 00:16:51,520
If you can't use managed identity, fine, and trap registration with a certificate, short

351
00:16:51,520 --> 00:16:54,280
lifetime stored in key vault rotated on a schedule.

352
00:16:54,280 --> 00:16:58,000
No secrets in scripts, not in dev, not just testing, not ever.

353
00:16:58,000 --> 00:16:59,000
Automation layer.

354
00:16:59,000 --> 00:17:02,680
Use Azure automation for simple schedules with runbooks that wake up, do one job and

355
00:17:02,680 --> 00:17:03,960
go back to sleep.

356
00:17:03,960 --> 00:17:06,440
Use functions for event driven flows.

357
00:17:06,440 --> 00:17:10,360
User created device status changed, license inventory dipped.

358
00:17:10,360 --> 00:17:14,280
Small, fast, cold start friendly when you're not dragging modules.

359
00:17:14,280 --> 00:17:19,320
GitHub actions for CICD and cross OS runners stick the same scripts in pipelines that validate

360
00:17:19,320 --> 00:17:20,760
then deploy to prod.

361
00:17:20,760 --> 00:17:25,080
Local power shell for validation and reproducible test before you throw anything at production,

362
00:17:25,080 --> 00:17:28,520
execution layer, power shell core plus invoke rest method.

363
00:17:28,520 --> 00:17:30,840
Version pin your endpoints V1.

364
00:17:30,840 --> 00:17:34,080
For stable, beta only behind feature flags with clear blast radius.

365
00:17:34,080 --> 00:17:35,320
Build two helpers once.

366
00:17:35,320 --> 00:17:41,480
Retry handler that honors 429 503 with exponential back off and jitter and a pager that follows

367
00:17:41,480 --> 00:17:42,480
at or data.

368
00:17:42,480 --> 00:17:45,280
Next link with guards, drop those helpers into every script.

369
00:17:45,280 --> 00:17:49,320
Suddenly your code is small, predictable and not stitched together with three connectors

370
00:17:49,320 --> 00:17:50,720
and a prayer.

371
00:17:50,720 --> 00:17:51,720
Configuration and secrets.

372
00:17:51,720 --> 00:17:57,440
Store non-secret config like group IDs, sqmaps, thresholds, in json or environment variables.

373
00:17:57,440 --> 00:18:01,440
Keep one config per environment so you don't hard code anything that will drift.

374
00:18:01,440 --> 00:18:03,160
Secrets and certs live in key vault.

375
00:18:03,160 --> 00:18:07,400
The code reads via managed identity, not a magic string in a ps1 file.

376
00:18:07,400 --> 00:18:11,400
Feature flags for dry run and confirm impact make rollout safe instead of theatrical.

377
00:18:11,400 --> 00:18:12,400
Observability.

378
00:18:12,400 --> 00:18:15,240
If you can't see your automation, you can't trust your automation.

379
00:18:15,240 --> 00:18:20,880
Send logs to log analytics, request id, correlation id, uri, method, status, duration retry

380
00:18:20,880 --> 00:18:21,880
count.

381
00:18:21,880 --> 00:18:25,040
Trace the whole flow with a run id so you can reconstruct what happened without calling

382
00:18:25,040 --> 00:18:26,280
six people.

383
00:18:26,280 --> 00:18:29,720
App insights for dependencies and live telemetry on functions.

384
00:18:29,720 --> 00:18:33,960
Change your monitor alerts on patterns that matter, failure rate spikes, throttle rate surges,

385
00:18:33,960 --> 00:18:36,160
SLA breaches, not every 404.

386
00:18:36,160 --> 00:18:37,160
Guard rails.

387
00:18:37,160 --> 00:18:40,480
Lease privilege map to jobs, not teams, pin versions.

388
00:18:40,480 --> 00:18:42,120
Review app consent squatterly.

389
00:18:42,120 --> 00:18:44,240
Dry run by default in new environments.

390
00:18:44,240 --> 00:18:48,360
And yes, Microsoft wants you here, not because it's cute, because modules can't keep up.

391
00:18:48,360 --> 00:18:51,760
This stack is faster to ship, simpler to govern and it doesn't panic when you move it from

392
00:18:51,760 --> 00:18:53,720
your laptop to Linux to a container.

393
00:18:53,720 --> 00:18:54,720
That's the point.

394
00:18:54,720 --> 00:18:56,680
Why Microsoft wants you on graph?

395
00:18:56,680 --> 00:19:00,840
Microsoft wants you on graph because it's the one surface they can actually ship to at speed.

396
00:19:00,840 --> 00:19:04,600
Identity devices, apps, content, the portal rights graph, so your code should too.

397
00:19:04,600 --> 00:19:05,600
Features hit graph first.

398
00:19:05,600 --> 00:19:07,600
Modules get love when someone finds the time.

399
00:19:07,600 --> 00:19:08,600
Sure.

400
00:19:08,600 --> 00:19:09,600
So is winning the lottery.

401
00:19:09,600 --> 00:19:10,600
Governance gets cleaner.

402
00:19:10,600 --> 00:19:14,600
O-auth scopes and consent tell you exactly who can do what and every call leaves a trail

403
00:19:14,600 --> 00:19:18,240
you can audit without rummaging through someone's profile script.

404
00:19:18,240 --> 00:19:19,240
Scale.

405
00:19:19,240 --> 00:19:23,320
The endpoints handle global traffic if you handle paging and back off like an adult.

406
00:19:23,320 --> 00:19:27,680
That from reality, Microsoft ships, PowerShell core, but graph is the constant.

407
00:19:27,680 --> 00:19:31,480
The thing most people miss is maintainers don't control product release speed.

408
00:19:31,480 --> 00:19:34,360
Rest schema changes land your code can adopt them that day.

409
00:19:34,360 --> 00:19:37,400
Beta has risk, pin versions and wrap with feature flags.

410
00:19:37,400 --> 00:19:39,240
Permissions will sprawl if you're lazy.

411
00:19:39,240 --> 00:19:40,440
Design roles per job.

412
00:19:40,440 --> 00:19:42,800
The portal is just a pretty face on top of graph.

413
00:19:42,800 --> 00:19:44,800
Don't be the last person to realize it.

414
00:19:44,800 --> 00:19:48,440
A best practices security reliability speed.

415
00:19:48,440 --> 00:19:49,440
Security first.

416
00:19:49,440 --> 00:19:53,040
Use managed identity wherever it exists when it doesn't set off speed secrets.

417
00:19:53,040 --> 00:19:55,800
Microsoft lifetimes rotate on schedule, store in key vault.

418
00:19:55,800 --> 00:19:58,520
Least privileged graph roles per job, not per team.

419
00:19:58,520 --> 00:20:01,560
Quarterly consent reviews or enjoy surprise outages.

420
00:20:01,560 --> 00:20:02,560
Reliability next.

421
00:20:02,560 --> 00:20:04,680
Handle pagination on every list endpoint.

422
00:20:04,680 --> 00:20:07,240
Detect 429503 on a retry after.

423
00:20:07,240 --> 00:20:09,360
Add exponential back off with jitter.

424
00:20:09,360 --> 00:20:10,760
E-dampotency everywhere.

425
00:20:10,760 --> 00:20:11,760
Check before change.

426
00:20:11,760 --> 00:20:12,760
Abset patterns.

427
00:20:12,760 --> 00:20:14,280
Use e-tags when available.

428
00:20:14,280 --> 00:20:16,240
Delta queries cut scan time and cost.

429
00:20:16,240 --> 00:20:17,240
Performance matters.

430
00:20:17,240 --> 00:20:19,120
Use select to trim payloads.

431
00:20:19,120 --> 00:20:20,600
Batch wear supported.

432
00:20:20,600 --> 00:20:23,000
Parallel with limits so you don't stampede the API.

433
00:20:23,000 --> 00:20:27,080
Cashestatic lookups like group IDs and SKU maps with a TTL.

434
00:20:27,080 --> 00:20:28,520
Observability isn't optional.

435
00:20:28,520 --> 00:20:33,240
Log request ID, correlation ID, URI methods, status, duration, retry count.

436
00:20:33,240 --> 00:20:34,920
Keep a run in for multi-step flows.

437
00:20:34,920 --> 00:20:38,880
Track success rate, P95 latency, throttle rate and delta efficiency.

438
00:20:38,880 --> 00:20:43,080
If your automation can't survive a 429, it's not automation, it's a suggestion.

439
00:20:43,080 --> 00:20:45,840
Write logs not feelings code hygiene keeps you sane.

440
00:20:45,840 --> 00:20:48,000
Small functions over 500 line scripts.

441
00:20:48,000 --> 00:20:51,640
Config driven via JSON or environment variables, no hard coded IDs.

442
00:20:51,640 --> 00:20:53,640
Triflex for dry run and save rollout.

443
00:20:53,640 --> 00:20:54,960
And yes, test your back off.

444
00:20:54,960 --> 00:20:57,640
Graph doesn't care about your feelings.

445
00:20:57,640 --> 00:20:59,400
The gotcha that ruins most graph scripts.

446
00:20:59,400 --> 00:21:00,880
Alright, the promised gotcha.

447
00:21:00,880 --> 00:21:04,360
This one ruins more graph scripts than anything else and it's not even interesting.

448
00:21:04,360 --> 00:21:05,600
Wrong token audience.

449
00:21:05,600 --> 00:21:07,440
You ask for a token to management.

450
00:21:07,440 --> 00:21:09,320
As your dot com, then you call graph.

451
00:21:09,320 --> 00:21:10,320
Microsoft dot com.

452
00:21:10,320 --> 00:21:13,680
And you stand there wondering why you got a 401 like it's a plot twist.

453
00:21:13,680 --> 00:21:14,680
It's not.

454
00:21:14,680 --> 00:21:16,080
Fix is dull and absolute.

455
00:21:16,080 --> 00:21:17,280
Audience must match resource.

456
00:21:17,280 --> 00:21:22,440
If you're using client credentials, you request for HTTPS, Graph, Microsoft dot com with

457
00:21:22,440 --> 00:21:25,320
the world default scope device code.

458
00:21:25,320 --> 00:21:26,320
Same story.

459
00:21:26,320 --> 00:21:30,280
Scopes for graph, not something you copied from an Azure arm tutorial in 2018.

460
00:21:30,280 --> 00:21:34,560
Managed identity, ask the local endpoint for graph, not whatever's in the sample.

461
00:21:34,560 --> 00:21:38,200
If you mess up your token audience, don't worry, you'll know immediately.

462
00:21:38,200 --> 00:21:41,160
Graph will reject you faster than a bad Tinder opener.

463
00:21:41,160 --> 00:21:43,040
Bonus trap pagination plus filtering.

464
00:21:43,040 --> 00:21:45,040
Not every property is filterable server side.

465
00:21:45,040 --> 00:21:46,720
If the docs say it isn't, believe them.

466
00:21:46,720 --> 00:21:50,760
Do what you can with filter on supported fields, then finish the cut in PowerShell.

467
00:21:50,760 --> 00:21:54,680
And when you mix search, count or advanced queries, remember the consistency level eventual

468
00:21:54,680 --> 00:21:59,320
header, or you'll get results that feel like they were assembled by a drunk spider.

469
00:21:59,320 --> 00:22:01,240
Sanity checklist before you hit run.

470
00:22:01,240 --> 00:22:02,960
Token audience is graph.

471
00:22:02,960 --> 00:22:06,520
Required scopes granted and admin consented pagination handled with guards.

472
00:22:06,520 --> 00:22:10,400
Retry with back off wired and tested, logging on every call with request id and correlation

473
00:22:10,400 --> 00:22:11,720
id.

474
00:22:11,720 --> 00:22:14,720
Scope it right or enjoy 401s and a long walk through

475
00:22:14,720 --> 00:22:15,720
the path.

476
00:22:15,720 --> 00:22:16,720
You'll see the future of the path.

477
00:22:16,720 --> 00:22:17,720
The future is pure graph.

478
00:22:17,720 --> 00:22:18,720
Here's the take away.

479
00:22:18,720 --> 00:22:22,520
The future of PowerShell is tokens, rest and graph, not modules.

480
00:22:22,520 --> 00:22:24,040
Move one thing to graph this week.

481
00:22:24,040 --> 00:22:25,720
Start with your worst module script.

482
00:22:25,720 --> 00:22:27,480
Ship the token headers call pattern.

483
00:22:27,480 --> 00:22:28,480
Use retries.

484
00:22:28,480 --> 00:22:29,480
Kill secrets.

485
00:22:29,480 --> 00:22:32,560
Then come back for the advanced patterns and my throttle save retry function.

486
00:22:32,560 --> 00:22:34,760
Next episode has the reusable auth wrappers.

487
00:22:34,760 --> 00:22:36,280
Stop living like it's 2016.

488
00:22:36,280 --> 00:22:37,280
Graph is the job now.

489
00:22:37,280 --> 00:22:38,280
Modules are nostalgic.

490
00:22:38,280 --> 00:22:39,280
Graph gets the job done.