Conditional Access target resources
Microsoft's edit ·
Microsoft's commit message in MicrosoftDocs/entra-docs · commit 51e90bb · +86 −23 lines · also on GitHub
⋯ 203 unchanged lines
204204
205205
```powershell
206206
# ==============================
207
−# Inventory of tenant-owned apps whose delegated consent grants include ONLY
207
+# Inventory of apps whose delegated consent grants include ONLY
208208
# the OIDC scopes + specific directory scopes listed below.
209209
#
210
+# Enhancements incorporated:
211
+# - Supported both PowerShell 5.1 and 7.x
212
+# - Add user sign-in count (last 7 days) per app
213
+#
210214
# Output:
211215
# - ServicePrincipalObjectId (oauth2PermissionGrants.clientId = SP object id)
216
+# - AppId
217
+# - AppDisplayName
218
+# - AppOwnerOrganizationId (for classification)
212219
# - Scopes (union of delegated scopes granted)
220
+# - UserSigninsLast7Days (Successful + Failed)
213221
# ==============================
214222
215223
$TenantId = Read-Host "Enter your Microsoft Entra tenant ID (GUID)"
⋯ 6 unchanged lines
222230
)
223231
224232
Disconnect-MgGraph -ErrorAction SilentlyContinue
233
+
225234
Connect-MgGraph -TenantId $TenantId -Scopes @(
226235
"DelegatedPermissionGrant.Read.All",
227
− "Directory.Read.All"
236
+ "Directory.Read.All",
237
+ "Reports.Read.All"
228238
)
229239
240
+# ------------------------------
230241
# Pull oauth2PermissionGrants (paging)
242
+# ------------------------------
231243
$uri = "https://graph.microsoft.com/beta/oauth2PermissionGrants?`$select=clientId,scope,consentType"
232244
$grants = @()
233245
while ($uri) {
⋯ 2 unchanged lines
236248
$uri = $resp.'@odata.nextLink'
237249
}
238250
239
−# Build baseline-only candidate set (paste-safe: no leading pipes)
240
−$candidates = @()
251
+# ------------------------------
252
+# Build baseline-only candidate set (Jun: HashSet per clientId)
253
+# ------------------------------
254
+$scopesByClient = @{} # key: clientId (SP objectId), value: HashSet[string] (case-insensitive)
241255
242
−$scopesByClient = @{} # key: clientId string, value: HashSet[string]
243256
foreach ($g in $grants) {
244257
$cid = $g.clientId.ToString().Trim()
258
+ if (-not $cid) { continue }
245259
246260
if (-not $scopesByClient.ContainsKey($cid)) {
247
− # Use HashSet for fast add + uniqueness
248261
$scopesByClient[$cid] = [System.Collections.Generic.HashSet[string]]::new(
249262
[System.StringComparer]::OrdinalIgnoreCase
250263
)
⋯ 8 unchanged lines
259272
260273
$candidates = foreach ($cid in $scopesByClient.Keys) {
261274
$scopes = $scopesByClient[$cid]
275
+ if ($scopes.Count -le 0) { continue }
262276
263
− # Outside baseline?
264277
$outside = $scopes | Where-Object { $_ -notin $BaselineScopes }
265
− if ($outside.Count -eq 0 -and $scopes.Count -gt 0) {
278
+ if ($outside.Count -eq 0) {
266279
[PSCustomObject]@{
267280
ServicePrincipalObjectId = $cid
268281
Scopes = ($scopes -join ' ')
⋯ 1 unchanged line
270283
}
271284
}
272285
286
+# ------------------------------
287
+# Pull per-app sign-in summary for last 7 days (Graph REST via Invoke-MgGraphRequest)
288
+# Endpoint: GET /beta/reports/getAzureADApplicationSignInSummary(period='D7')
289
+# In this API output, 'id' corresponds to the appId (clientId)
290
+# ------------------------------
291
+$signInSummary = @()
292
+$signInUri = "https://graph.microsoft.com/beta/reports/getAzureADApplicationSignInSummary(period='D7')"
293
+
294
+while ($signInUri) {
295
+ $resp = Invoke-MgGraphRequest -Method GET -Uri $signInUri
296
+
297
+ if ($resp -and $resp.value) {
298
+ $signInSummary += $resp.value
299
+ }
300
+
301
+ # Paging (if present)
302
+ $signInUri = $resp.'@odata.nextLink'
303
+}
304
+
305
+# appId -> total sign-ins (7d)
306
+$signInCountByAppId = @{}
307
+foreach ($s in $signInSummary) {
308
+ $appId = $s.id
309
+ if (-not $appId) { continue }
310
+
311
+ # PS5.1-safe null handling
312
+ $success = 0
313
+ $failed = 0
314
+ if ($null -ne $s.successfulSignInCount) { $success = [int]$s.successfulSignInCount }
315
+ if ($null -ne $s.failedSignInCount) { $failed = [int]$s.failedSignInCount }
316
+
317
+ $signInCountByAppId[$appId] = $success + $failed
318
+}
319
+
273320
$resultsTenantOwned = @()
274321
$resultsNotTenantOwned = @()
275322
276
−# Filter to tenant-owned or external apps
323
+# ------------------------------
324
+# Filter to tenant-owned or external apps; enrich with appId/displayName + sign-in counts
325
+# ------------------------------
277326
foreach ($c in $candidates) {
278327
try {
279
− $spUri = "https://graph.microsoft.com/beta/servicePrincipals/$($c.ServicePrincipalObjectId)?`$select=id,appOwnerOrganizationId"
328
+ $spUri = "https://graph.microsoft.com/beta/servicePrincipals/$($c.ServicePrincipalObjectId)?`$select=id,appId,displayName,appOwnerOrganizationId"
280329
$sp = Invoke-MgGraphRequest -Method GET -Uri $spUri
281330
331
+ $signinCount7d = 0
332
+ if ($sp.appId -and $signInCountByAppId.ContainsKey($sp.appId)) {
333
+ $signinCount7d = $signInCountByAppId[$sp.appId]
334
+ }
335
+
336
+ $row = [PSCustomObject]@{
337
+ ServicePrincipalObjectId = $c.ServicePrincipalObjectId
338
+ AppId = $sp.appId
339
+ AppDisplayName = $sp.displayName
340
+ AppOwnerOrganizationId = $sp.appOwnerOrganizationId
341
+ Scopes = $c.Scopes
342
+ UserSigninsLast7Days = $signinCount7d
343
+ }
344
+
282345
if ($sp.appOwnerOrganizationId -eq $TenantId) {
283
− $resultsTenantOwned += [PSCustomObject]@{
284
− ServicePrincipalObjectId = $c.ServicePrincipalObjectId
285
− Scopes = $c.Scopes
286
− }
346
+ $resultsTenantOwned += $row
287347
}
288348
else {
289
− $resultsNotTenantOwned += [PSCustomObject]@{
290
− ServicePrincipalObjectId = $c.ServicePrincipalObjectId
291
− Scopes = $c.Scopes
292
− }
349
+ $resultsNotTenantOwned += $row
293350
}
294351
}
295352
catch {
296
− # Ignore non-enumerable / non-tenant-owned service principals
353
+ # Ignore non-enumerable / missing service principals
297354
}
298355
}
299356
357
+# ------------------------------
300358
# Output
301
−'=== All tenant-owned apps whose delegated consent grants include ONLY the OIDC scopes + specific directory scopes ==='
302
−$resultsTenantOwned | Sort-Object ServicePrincipalObjectId
359
+# ------------------------------
360
+'=== Tenant-owned apps whose delegated consent grants include ONLY baseline scopes + user sign-ins (last 7 days) ==='
361
+$resultsTenantOwned |
362
+ Sort-Object UserSigninsLast7Days -Descending |
363
+ Format-Table -AutoSize
303364
304
−'=== All external apps whose delegated consent grants include ONLY the OIDC scopes + specific directory scopes ==='
305
−$resultsNotTenantOwned | Sort-Object ServicePrincipalObjectId
365
+'=== External apps whose delegated consent grants include ONLY baseline scopes + user sign-ins (last 7 days) ==='
366
+$resultsNotTenantOwned |
367
+ Sort-Object UserSigninsLast7Days -Descending |
368
+ Format-Table -AutoSize
306369
```
307370
308371
#### [Usage and Insights report](#tab/usage-and-insights-report)
⋯ 120 unchanged lines
Microsoft's Markdown source from MicrosoftDocs/entra-docs, © Microsoft Corporation, under MIT. Changed lines with up to 3 unchanged lines around each; ChangeIntel kept this copy 10 Oct 04:32 UTC. The commit date is when the source changed, which can be hours or days before Learn published it.