Back to script library
Entra / Microsoft 365 · Applications

Report delegated permissions

A script to demonstrate how to report delegated permissions held by Entra ID user accounts.

Connect & set up

Run these once per session. All scopes are read-only unless the script makes changes.

Connect-MgGraph -NoWelcome -Scopes Directory.Read.All

Run it

The main script. Copy it, or download the .ps1 and run it from your console.

Connect-MgGraph -NoWelcome -Scopes Directory.Read.All
# Find licensed users
[array]$Users = Get-MgUser -Filter "assignedLicenses/`$count ne 0 and userType eq 'Member'" `
-ConsistencyLevel eventual -CountVariable Records -All -Sort displayName
Write-Host ("{0} licensed user accounts found" -f $Users.count)
If (!($Users)) {
Write-Host "No licensed users found - exiting!"; break
}
# Define file name for CSV output
$CSVOutputFile = ((New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path) + "\DelegatedPermissions.csv"
# Scopes to ignore for reporting purposes
[array]$IgnoredScopes = "openid", "profile", "offline_access"
# Create hash tables for lookup of client and resource names
$ClientIds = @{}
$ResourceIds = @{}
$Report = [System.Collections.Generic.List[Object]]::new()
ForEach ($User in $Users) {
Write-Host ("Checking delegated permissions for {0}" -f $User.UserPrincipalName)
[array]$Permissions = Get-MgUserOauth2PermissionGrant -UserId $User.Id -All
ForEach ($Permission in $Permissions) {
# Try to look up client and resource names in the hash tables. If we find an entry, use the
# display name, else run Get-MgServicePrincipal to find the display name and store it
$ClientDisplayName = $ClientIds[$Permission.ClientId]
If ($null -eq $ClientDisplayName) {
$ClientDisplayName = (Get-MgServicePrincipal -ServicePrincipalId $Permission.ClientId).displayName
$ClientIds.Add($Permission.ClientId, $ClientDisplayName)
}
$ResourceDisplayName = $ResourceIds[$Permission.ResourceId]
If ($null -eq $ResourceDisplayName) {
$ResourceDisplayName = (Get-MgServicePrincipal -ServicePrincipalId $Permission.ResourceId).displayName
$ResourceIds.Add($Permission.ResourceId, $ResourceDisplayName)
}
# Find the set of assigned scopes, ignoring some of the common scopes
[array]$Scopes = $Permission.scope.Split(" ")
[array]$FoundScopes = $null
ForEach ($Scope in $Scopes) {
If ($Scope -in $IgnoredScopes -or [string]::isNullOrWhiteSpace($Scope)) {
Continue
}
$FoundScopes += $Scope
}
# Generate the output
$ReportLine = [PSCustomObject][Ordered]@{
'Consent type' = $Permission.consentType
UserPrincipalName = $User.UserPrincipalName
Client = $ClientDisplayName
Resource = $ResourceDisplayName
Scopes = $FoundScopes -join ", "
ClientId = $Permission.ClientId
}
$Report.Add($ReportLine)
}
}
# Now handle the AppPrincipals delegated permissions
Write-Host "Checking delegated permissions for all user accounts (AllPrincipals)"
[array]$AllPermissions = Get-MgOauth2PermissionGrant -filter "consentType eq 'AllPrincipals'" -All
Write-Host ("{0} delegated permissions found for all user accounts" -f $AllPermissions.count)
[int]$i = 0
ForEach ($AllPermission in $AllPermissions) {
$i++
# Try to look up client and resource names in the hash tables. If we find an entry, use the
# display name, else run Get-MgServicePrincipal to find the display name and store it
$ClientDisplayName = $ClientIds[$AllPermission.ClientId]
If ($null -eq $ClientDisplayName) {
$ClientDisplayName = (Get-MgServicePrincipal -ServicePrincipalId $AllPermission.ClientId).displayName
$ClientIds.Add($AllPermission.ClientId, $ClientDisplayName)
}
Write-Host ("Checking permission for client {0} ({1}/{2})" -f $ClientDisplayName, $i, $AllPermissions.count)
$ResourceDisplayName = $ResourceIds[$AllPermission.ResourceId]
If ($null -eq $ResourceDisplayName) {
$ResourceDisplayName = (Get-MgServicePrincipal -ServicePrincipalId $AllPermission.ResourceId).displayName
$ResourceIds.Add($AllPermission.ResourceId, $ResourceDisplayName)
}
# Find the set of assigned scopes, ignoring some of the common scopes
[array]$Scopes = $AllPermission.scope.Split(" ")
[array]$FoundScopes = $null
ForEach ($Scope in $Scopes) {
If ($Scope -in $IgnoredScopes -or [string]::isNullOrWhiteSpace($Scope)) {
Continue
}
$FoundScopes += $Scope
}
# Generate the output
$ReportLine = [PSCustomObject][Ordered]@{
'Consent type' = $AllPermission.consentType
UserPrincipalName = "All User Accounts"
Client = $ClientDisplayName
Resource = $ResourceDisplayName
Scopes = $FoundScopes -join ", "
ClientId = $AllPermission.ClientId
}
$Report.Add($ReportLine)
}
$Report | Out-GridView -Title "Delegated Permissions Report"
$Report | Export-Csv -Path $CSVOutputFile -NoTypeInformation -Encoding UTF8
Write-Host ("CSV output file written to {0}" -f $CSVOutputFile)
Attribution