Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Script to list all delegated permissions and application permissions in Azure AD
<#
.SYNOPSIS
Lists delegated permissions (OAuth2PermissionGrants) and application permissions (AppRoleAssignments).
.PARAMETER DelegatedPermissions
If set, will return delegated permissions. If neither this switch nor the ApplicationPermissions switch is set,
both application and delegated permissions will be returned.
.PARAMETER ApplicationPermissions
If set, will return application permissions. If neither this switch nor the DelegatedPermissions switch is set,
both application and delegated permissions will be returned.
.PARAMETER ShowProgress
Whether or not to display a progress bar when retrieving application permissions (which could take some time).
.PARAMETER PrecacheSize
The number of users to pre-load into a cache. For tenants with over a thousand users,
increasing this may improve performance of the script.
.EXAMPLE
PS C:\> .\Get-AzureADPSPermissions.ps1 | Export-Csv -Path "permissions.csv" -NoTypeInformation
Generates a CSV report of all permissions granted to all apps.
.EXAMPLE
PS C:\> .\Get-AzureADPSPermissions.ps1 -ApplicationPermissions -ShowProgress | Where-Object { $_.Permission -eq "Directory.Read.All" }
Get all apps which have application permissions for Directory.Read.All.
#>
[CmdletBinding()]
param(
[switch] $DelegatedPermissions,
[switch] $ApplicationPermissions,
[switch] $ShowProgress,
[int] $PrecacheSize = 999
)
# Get tenant details to test that Connect-AzureAD has been called
try {
$tenant_details = Get-AzureADTenantDetail
} catch {
throw "You must call Connect-AzureAD before running this script."
}
Write-Verbose ("TenantId: {0}, InitialDomain: {1}" -f `
$tenant_details.ObjectId, `
($tenant_details.VerifiedDomains | Where-Object { $_.Initial }).Name)
# An in-memory cache of objects by {object ID} andy by {object class, object ID}
$script:ObjectByObjectId = @{}
$script:ObjectByObjectClassId = @{}
# Function to add an object to the cache
function CacheObject($Object) {
if ($Object) {
if (-not $script:ObjectByObjectClassId.ContainsKey($Object.ObjectType)) {
$script:ObjectByObjectClassId[$Object.ObjectType] = @{}
}
$script:ObjectByObjectClassId[$Object.ObjectType][$Object.ObjectId] = $Object
$script:ObjectByObjectId[$Object.ObjectId] = $Object
}
}
# Function to retrieve an object from the cache (if it's there), or from Azure AD (if not).
function GetObjectByObjectId($ObjectId) {
if (-not $script:ObjectByObjectId.ContainsKey($ObjectId)) {
Write-Verbose ("Querying Azure AD for object '{0}'" -f $ObjectId)
try {
$object = Get-AzureADObjectByObjectId -ObjectId $ObjectId
CacheObject -Object $object
} catch {
Write-Verbose "Object not found."
}
}
return $script:ObjectByObjectId[$ObjectId]
}
# Function to retrieve all OAuth2PermissionGrants, either by directly listing them (-FastMode)
# or by iterating over all ServicePrincipal objects. The latter is required if there are more than
# 999 OAuth2PermissionGrants in the tenant, due to a bug in Azure AD.
function GetOAuth2PermissionGrants([switch]$FastMode) {
if ($FastMode) {
Get-AzureADOAuth2PermissionGrant -All $true
} else {
$script:ObjectByObjectClassId['ServicePrincipal'].GetEnumerator() | ForEach-Object { $i = 0 } {
if ($ShowProgress) {
Write-Progress -Activity "Retrieving delegated permissions..." `
-Status ("Checked {0}/{1} apps" -f $i++, $servicePrincipalCount) `
-PercentComplete (($i / $servicePrincipalCount) * 100)
}
$client = $_.Value
Get-AzureADServicePrincipalOAuth2PermissionGrant -ObjectId $client.ObjectId
}
}
}
# Get all ServicePrincipal objects and add to the cache
Write-Verbose "Retrieving all ServicePrincipal objects..."
Get-AzureADServicePrincipal -All $true | ForEach-Object {
CacheObject -Object $_
}
$servicePrincipalCount = $script:ObjectByObjectClassId['ServicePrincipal'].Count
if ($DelegatedPermissions -or (-not ($DelegatedPermissions -or $ApplicationPermissions))) {
# Get one page of User objects and add to the cache
Write-Verbose ("Retrieving up to {0} User objects..." -f $PrecacheSize)
Get-AzureADUser -Top $PrecacheSize | Where-Object {
CacheObject -Object $_
}
Write-Verbose "Testing for OAuth2PermissionGrants bug before querying..."
$fastQueryMode = $false
try {
# There's a bug in Azure AD Graph which does not allow for directly listing
# oauth2PermissionGrants if there are more than 999 of them. The following line will
# trigger this bug (if it still exists) and throw an exception.
$null = Get-AzureADOAuth2PermissionGrant -Top 999
$fastQueryMode = $true
} catch {
if ($_.Exception.Message -and $_.Exception.Message.StartsWith("Unexpected end when deserializing array.")) {
Write-Verbose ("Fast query for delegated permissions failed, using slow method...")
} else {
throw $_
}
}
# Get all existing OAuth2 permission grants, get the client, resource and scope details
Write-Verbose "Retrieving OAuth2PermissionGrants..."
GetOAuth2PermissionGrants -FastMode:$fastQueryMode | ForEach-Object {
$grant = $_
if ($grant.Scope) {
$grant.Scope.Split(" ") | Where-Object { $_ } | ForEach-Object {
$scope = $_
$client = GetObjectByObjectId -ObjectId $grant.ClientId
$resource = GetObjectByObjectId -ObjectId $grant.ResourceId
$principalDisplayName = ""
if ($grant.PrincipalId) {
$principal = GetObjectByObjectId -ObjectId $grant.PrincipalId
$principalDisplayName = $principal.DisplayName
}
New-Object PSObject -Property ([ordered]@{
"PermissionType" = "Delegated"
"ClientObjectId" = $grant.ClientId
"ClientDisplayName" = $client.DisplayName
"ResourceObjectId" = $grant.ResourceId
"ResourceDisplayName" = $resource.DisplayName
"Permission" = $scope
"ConsentType" = $grant.ConsentType
"PrincipalObjectId" = $grant.PrincipalId
"PrincipalDisplayName" = $principalDisplayName
})
}
}
}
}
if ($ApplicationPermissions -or (-not ($DelegatedPermissions -or $ApplicationPermissions))) {
# Iterate over all ServicePrincipal objects and get app permissions
Write-Verbose "Retrieving AppRoleAssignments..."
$script:ObjectByObjectClassId['ServicePrincipal'].GetEnumerator() | ForEach-Object { $i = 0 } {
if ($ShowProgress) {
Write-Progress -Activity "Retrieving application permissions..." `
-Status ("Checked {0}/{1} apps" -f $i++, $servicePrincipalCount) `
-PercentComplete (($i / $servicePrincipalCount) * 100)
}
$sp = $_.Value
Get-AzureADServiceAppRoleAssignedTo -ObjectId $sp.ObjectId -All $true `
| Where-Object { $_.PrincipalType -eq "ServicePrincipal" } | ForEach-Object {
$assignment = $_
$client = GetObjectByObjectId -ObjectId $assignment.PrincipalId
$resource = GetObjectByObjectId -ObjectId $assignment.ResourceId
$appRole = $resource.AppRoles | Where-Object { $_.Id -eq $assignment.Id }
New-Object PSObject -Property ([ordered]@{
"PermissionType" = "Application"
"ClientObjectId" = $assignment.PrincipalId
"ClientDisplayName" = $client.DisplayName
"ResourceObjectId" = $assignment.ResourceId
"ResourceDisplayName" = $resource.DisplayName
"Permission" = $appRole.Value
})
}
}
}
@evgaff

This comment has been minimized.

Copy link

commented Oct 25, 2018

Thanks for posting this. I've been trying to get this work by looking at each part of the script as I have to work with a huge directory.
Have you ever seen this? When running Get-AzureADOAuth2PermissionGrant -all $true it always errors out:
Get-AzureADOAuth2PermissionGrant : Unexpected end when deserializing array. Path 'value[1000]', line 1, position
359298.

I've updated to the latest AzureADPreview 2.0.2.5 and tried from multiple machines and accounts (into the same directory).

Interestingly, I can do -top 57, which returns exactly 1000 objects. When set to 58, it fails.

Any thoughts?

@shesha1

This comment has been minimized.

Copy link

commented Nov 28, 2018

Thanks for this post.

the command get-AzureADOAuth2PermissionGrant is returning only finite number of results. If i run "get-AzureADOAuth2PermissionGrant -all $true" it is throwing the below error.
Get-AzureADOAuth2PermissionGrant : Unexpected end when deserializing array. Path 'value[1000]', line 1, position 366740.
At line:1 char:1

  • Get-AzureADOAuth2PermissionGrant -all $true
  •   + CategoryInfo          : NotSpecified: (:) [Get-AzureADOAuth2PermissionGrant], ApiException
      + FullyQualifiedErrorId : Microsoft.Open.AzureAD16.Client.ApiException,Microsoft.Open.AzureAD16.PowerShell.GetOAuth2PermissionGrants
    
    

Please let me know if you have any workaround for the same.

Is there any other command which provides the user scopes?

Thanks,
Shesh

@cwitjes

This comment has been minimized.

Copy link

commented Dec 13, 2018

You can actually modify the script to read the permissions per Azure AD Service Prinicipal and using

Get-AzureADServicePrincipalOAuth2PermissionGrant -ObjectId $sp.ObjectId

instead of "Get-AzureADOAuth2PermissionGrant -all $true"

Snipset:

Get all registered applications

$AzSPAll = get-AzureADServicePrincipal -All $true

.
.

Get all existing OAuth2 permission grants, get the client, resource and scope details

Write-Verbose "Retrieving OAuth2PermissionGrants...per SP"

foreach($sp in $AzSPAll){
       
Get-AzureADServicePrincipalOAuth2PermissionGrant -ObjectId $sp.ObjectId | ForEach-Object {
    $grant = $_
    if ($grant.Scope) {

.
.
at least this worked for me.

hth,

Claus

@shesha1

This comment has been minimized.

Copy link

commented Dec 14, 2018

Hi Claus,

Thanks for the solution. It worked like a charm!!

below is the format I am using..

$azurespnall = Get-AzureADServicePrincipal -all $true

Foreach ($spn in $azurespnall)
    {
    #$spn = "053f4ac8-8329-4ef8-993e-dca44c6150be"
    Get-AzureADServicePrincipalOAuth2PermissionGrant -ObjectId $spn.objectid | ForEach-Object { 
        
        $scope = $_

        $client = Get-AzureADObjectByObjectId -ObjectIds $spn.objectid
        $resource = Get-AzureADObjectByObjectId -ObjectIds $scope.Resourceid
        $principalDisplayname = ""
        if($scope.PrincipalID)
            {

            $principal = Get-AzureADObjectByObjectId -ObjectIds $scope.principalID

            $principalDisplayname = $principal.DisplayName
            }

           
           $scope.clientid
           $client.DisplayName
           $scope.Resourceid
           $resource.DisplayName
           $scope.Scope
           $principalDisplayname
           $principalDisplayname

     }

Regards,
Shesh

@psignoret

This comment has been minimized.

Copy link
Owner Author

commented Dec 18, 2018

@evgaff @shesha1 There's currently a bug in Azure AD when you have more than 1000 OAuth2PermissionGrants (delegated permission grants) in the tenant. As @cwitjes rightly points out, a workaround available today is to query these from each ServicePrincipal object's. Unfortunately, this is orders of magnitude slower than the original approach.

I've updated the script to test for the bug, and if it is triggered (because it hasn't been fixed and your tenant has more than 1000 grants), then it will fall back to the per-object (slow) query.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.