PowerShell + Graph API the Modern Way

Mirko PetersPodcasts3 hours ago13 Views


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.





Source link

0 Votes: 0 Upvotes, 0 Downvotes (0 Points)

Leave a reply

Join Us
  • X Network2.1K
  • LinkedIn3.8k
  • Bluesky0.5K
Support The Site
Events
December 2025
MTWTFSS
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     
« Nov   Jan »
Follow
Search
Popular Now
Loading

Signing-in 3 seconds...

Signing-up 3 seconds...