Get-MgAllAppsByPermission.ps1
<#PSScriptInfo
.VERSION 0.8 .GUID 84a7dc33-b92a-4911-a0a3-f319bb65a059 .AUTHOR Daniel Bradley .TAGS Microsoft Graph PowerShell Application Permissions .RELEASENOTES [Version 0.0.2] - Initial Release. [Version 0.0.3] - Fixed dependant module install. [Version 0.0.4] - Add ScopeType and orderd results. [Version 0.0.5] - Removed placeholder permission. [Version 0.0.6] - Added error handling. [Version 0.0.7] - Added -All parameter [Version 0.0.8] - Updated response #> <# .DESCRIPTION Finds all delegated and application permissions for applications in Entra by permission scope. .SYNOPSIS Loops through all applications and filters for the defined permissions. .EXAMPLE # Find all apps with a specific permission assigned Get-MgAllAppsByPermission -Scope Mail.Send # Find all apps with any permission assigned Get-MgAllAppsByPermission -All #> [CmdletBinding()] param ( # Switch to display the current script version [switch]$Version, # String to define the permission scope search [string]$Scope, # Swith to display all permissions assigned to all applications [Switch]$All ) # Script information $CurrentVersion = '0.8' if ($Version.IsPresent) { $CurrentVersion exit 0 } #-------------Dependencies-------------# $module = Import-Module microsoft.graph.authentication -PassThru -ErrorAction Ignore if (-not $module) { Write-Host "Installing module microsoft.graph.authentication" -ForegroundColor Yellow Install-Module microsoft.graph.authentication -Scope CurrentUser -Force } Import-Module microsoft.graph.authentication $module = Import-Module microsoft.graph.applications -PassThru -ErrorAction Ignore if (-not $module) { Write-Host "Installing module microsoft.graph.applications" -ForegroundColor Yellow Install-Module microsoft.graph.applications -Scope CurrentUser -Force } Import-Module microsoft.graph.applications $module = Import-Module Microsoft.Graph.Identity.SignIns -PassThru -ErrorAction Ignore if (-not $module) { Write-Host "Installing module microsoft.graph.Identity.SignIns" -ForegroundColor Yellow Install-Module Microsoft.Graph.Identity.SignIns -Scope CurrentUser -Force } Import-Module Microsoft.Graph.Identity.SignIns #-------------End Dependencies-------------# #----------------Functions----------------# function Get-AllAppsByPermission { <# .SYNOPSIS Retrieves all apps in Microsoft Entra by defined permission. .DESCRIPTION This function queries both delegated permissions and application (app roles) permissions on applications in Microsoft Entra. Then filters locally for only those with the defined permission. .EXAMPLE Get-AllAppsByPermission -Scope Mail.Send This example will find all applications with Mail.Send permission consented. .NOTES Credit to Luca Spolidoro @ Microsoft for sharing some core functionality of this script #> [CmdletBinding()] Param( $Permission, [switch]$all ) #Check current context If((Get-MgContext).Scopes -notcontains "Application.Read.All"){ Write-host "Connecting to Microsoft Graph..." Connect-MgGraph -Scopes Application.Read.All, DelegatedPermissionGrant.Read.All } #The the Graph Service Principal ID $graphSp = Get-MgServicePrincipalByAppId -AppId "00000003-0000-0000-c000-000000000000" -Property "id,appRoles" #Get all OAuth2 delegated Graph permissions for all apps $delegatedPermissions = Get-MgOauth2PermissionGrant -All -Top 999 -CountVariable CountVar -Property "clientId,scope" -Filter "ConsentType eq 'AllPrincipals' and resourceId eq '$($graphSp.Id)'" -Headers @{ 'ConsistencyLevel' = 'Eventual' } | Select-Object @{Name='ServicePrincipalId';Expression={$_.ClientId}}, @{Name='ScopeType';Expression={"Delegated"}}, @{Name='Scope'; Expression={$_.Scope -split ' ' -ne '' }} #Get all possible available app roles $graphAppRoles = $graphSp | Select-Object -ExpandProperty AppRoles | Select-Object Id, Value | Group-Object -Property Id -AsHashTable #Get all app role assignments for all apps $spRoleAssignments = Get-MgServicePrincipal -All -PageSize 999 -ExpandProperty "appRoleAssignments" -Property "id,appId,displayName" | Where-Object { ($_.AppRoleAssignments | Where-Object { $_.ResourceId -eq $graphSp.Id }).Count -gt 0 } | Select-Object Id, AppId, DisplayName, @{Name="AppRoleId"; Expression={ ($_.AppRoleAssignments).AppRoleId }} #Expand permissions for all app role assignments $appPermissions = $spRoleAssignments | ForEach-Object { [PSCustomObject]@{ ServicePrincipalId = $_.Id; ScopeType="Application"; Scope = @($graphAppRoles[$_.AppRoleId].Value)}} # Switch for all permissions to specific permission If ($all.IsPresent) { $filteredPermissions = $delegatedPermissions + $appPermissions } Else { #Filter all permissions by selected permission $filteredPermissions = $delegatedPermissions + $appPermissions | Where-Object Scope -CEQ $Permission } #if applications list is greater that 15, batch requests to ensure filter OR limit is not reached. If ($filteredPermissions.Count -gt 15){ #Initilise variables $Filters = @() $i=0 #Loop to generate filter strings (To solve max of 15 OR collection limit for Graph API) for($i=0;$i -lt $filteredPermissions.count;$i+=14){ $filterString = "id in('" + (($filteredPermissions[$i..($i+14)] | Select-Object -ExpandProperty ServicePrincipalId) -join "','") + "')" $Filters += $filterString } #Generate the batch payload $batch = [System.Collections.Generic.List[Object]]::new() Foreach ($Filter in $Filters){ $x += 1 $Obj = [PSCustomObject]@{ id = $x method = "GET" URL = "/servicePrincipals?`$filter=$Filter&`$select=id,appId,displayName" } $batch.add($Obj) } $BatchBody = [PSCustomObject]@{requests = $batch} | ConvertTo-Json -Depth 4 #Send Batch request $filteredAppsWithName = (Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/$batch' -Body $BatchBody -ContentType 'application/json' -OutputType PSObject).responses.body.Value } Else { #Send single request $filterString = "id in('" + (($filteredPermissions | Select-Object -ExpandProperty ServicePrincipalId) -join "','") + "')" $filteredAppsWithName = Get-MgServicePrincipal -Filter $filterString -Select "id,appId,displayName" -erroraction SilentlyContinue | Select-Object Id, AppId, DisplayName } #Initialise array $Report = [System.Collections.Generic.List[Object]]::new() #Loop through the unique name of each app only to avoid duplicate entries ForEach ($app in ($filteredAppsWithName | Select -Unique DisplayName, id, Appid)){ #Get scope types for each app/permission $Found = @() $Found = $filteredPermissions | Where {$_.ServicePrincipalId -eq $app.Id} #Loop through each permission type to add content to report Foreach ($perm in $found){ $obj = [PSCustomObject][ordered]@{ "DisplayName" = $app.DisplayName "AppId" = $app.AppId "ID" = $app.Id "ScopeType" = $perm.ScopeType "Permissions" = ($filteredPermissions | Where {($_.ServicePrincipalId -eq $app.Id) -and $_.ScopeType -eq $perm.ScopeType} | Select -expand Scope) -join ", " } $report.Add($obj) } } #Check if -All has been defined If ($all.IsPresent){ Write-Host "$(($Report | Select -Unique DisplayName).count) apps found" -ForegroundColor Cyan } Else{ Write-Host "$($report.count) apps found with permission: $Permission `n" -ForegroundColor Cyan } #Report warning if count equals 0 If ($Report.count -eq 0){ Write-Host "Permissions are case senstive, please ensure letter capitalisation is correct `n" -ForegroundColor Yellow } $Report } #----------------End Functions----------------# #Selector If ($all.IsPresent){ Get-AllAppsByPermission -All } ElseIf ($null -ne $scope){ Get-AllAppsByPermission -Permission $Scope } |