Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources or documents need attention: 258/260 feeds/APIs · 416/439 docs · checked 22:55 UTC Customize Public modeDiscuss ChangeIntel on Discord

Conditional Access target resources

This page's changes and edits · All guidance changes

Microsoft's edit ·

Update PowerShell script for app inventory and sign-ins

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.

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 20 Oct 22:55 UTC · 258 of 260 readable · 256 fully read · documentation 416/439 current