<# .SYNOPSIS Lists the API permissions held by the enterprise apps in your tenant, for the azureblog.co.uk app permission risk check: https://azureblog.co.uk/tools/app-risk/ .DESCRIPTION Read-only. Reads service principals, the application permissions (app role assignments) granted to them and the delegated permission grants, then saves azureblog-app-permissions-.json in the current folder. The file contains app names and permission names. Load it at the address above; it's read in your browser and never uploaded. Needs the Microsoft.Graph.Authentication module and someone who can consent to Application.Read.All and Directory.Read.All (for example a Global Reader once an admin has consented). Microsoft's own first-party apps are skipped unless you add -IncludeMicrosoftApps. .EXAMPLE .\Get-AzureBlogAppPermissions.ps1 #> [CmdletBinding()] param([switch]$IncludeMicrosoftApps) Connect-MgGraph -Scopes "Application.Read.All", "Directory.Read.All" -NoWelcome $tenantId = (Get-MgContext).TenantId function Get-All([string]$Uri) { $items = [System.Collections.Generic.List[object]]::new() while ($Uri) { $page = Invoke-MgGraphRequest -Method GET -Uri $Uri foreach ($i in $page.value) { $items.Add($i) } $Uri = $page.'@odata.nextLink' } $items } # Tenants that own Microsoft's first-party apps $microsoft = @("f8cdef31-a31e-4b4a-93e4-5f571e91255a", "72f988bf-86f1-41af-91ab-2d7cd011db47") Write-Host "Reading service principals..." $sps = Get-All "https://graph.microsoft.com/v1.0/servicePrincipals?`$select=id,appId,displayName,appOwnerOrganizationId,servicePrincipalType,accountEnabled,verifiedPublisher,appRoles&`$top=100" $byId = @{} foreach ($sp in $sps) { $byId[$sp.id] = $sp } Write-Host "Reading application permissions..." $appPerms = @{} foreach ($res in ($sps | Where-Object { $_.appRoles.Count -gt 0 })) { $roles = @{} foreach ($r in $res.appRoles) { $roles[$r.id] = $r.value } foreach ($a in (Get-All "https://graph.microsoft.com/v1.0/servicePrincipals/$($res.id)/appRoleAssignedTo")) { if ($a.principalType -ne "ServicePrincipal") { continue } if (-not $appPerms.ContainsKey($a.principalId)) { $appPerms[$a.principalId] = [System.Collections.Generic.List[object]]::new() } $name = if ($roles.ContainsKey($a.appRoleId)) { $roles[$a.appRoleId] } else { "(default access)" } $appPerms[$a.principalId].Add([ordered]@{ r = $res.displayName; p = $name }) } } Write-Host "Reading delegated permission grants..." $delegated = @{} foreach ($g in (Get-All "https://graph.microsoft.com/v1.0/oauth2PermissionGrants")) { $resName = if ($byId.ContainsKey($g.resourceId)) { $byId[$g.resourceId].displayName } else { $g.resourceId } if (-not $delegated.ContainsKey($g.clientId)) { $delegated[$g.clientId] = @{} } foreach ($s in ($g.scope -split " ")) { if (-not $s) { continue } $key = "$resName|$s|$($g.consentType)" if ($delegated[$g.clientId].ContainsKey($key)) { $delegated[$g.clientId][$key].n++ } else { $delegated[$g.clientId][$key] = [ordered]@{ r = $resName; p = $s; c = $g.consentType; n = 1 } } } } $apps = foreach ($sp in $sps) { # @() around everything so single items and empty results still come out as JSON arrays $ap = @($appPerms[$sp.id] | Where-Object { $_ }) $dp = @(if ($delegated.ContainsKey($sp.id)) { $delegated[$sp.id].Values }) if ($ap.Count -eq 0 -and $dp.Count -eq 0) { continue } $isMicrosoft = $microsoft -contains $sp.appOwnerOrganizationId if ($isMicrosoft -and -not $IncludeMicrosoftApps) { continue } $owner = if ($isMicrosoft) { "Microsoft" } elseif ($sp.appOwnerOrganizationId -eq $tenantId) { "this tenant" } else { "external" } [ordered]@{ name = $sp.displayName appId = $sp.appId type = $sp.servicePrincipalType enabled = $sp.accountEnabled publisher = $sp.verifiedPublisher.displayName owner = $owner app = $ap delegated = $dp } } $file = "azureblog-app-permissions-$(Get-Date -Format yyyy-MM-dd).json" [ordered]@{ tool = "azureblog-app-permissions"; version = 1; generated = (Get-Date).ToString("s"); apps = @($apps) } | ConvertTo-Json -Depth 6 | Set-Content -Path $file -Encoding utf8 Write-Host "Saved $file with $(@($apps).Count) apps. Load it at https://azureblog.co.uk/tools/app-risk/"