internal/functions/Get-AzureADPSPermission.ps1

Function Get-AzureADPSPermission {
<#
.SYNOPSIS
    Lists delegated permissions (OAuth2PermissionGrants) and application permissions (AppRoleAssignments).
 
.DESCRIPTION
    Lists delegated permissions (OAuth2PermissionGrants) and application permissions (AppRoleAssignments)
    using Microsoft Graph API. This function retrieves and formats permission information for analysis
    of application and delegated permissions in your tenant.
 
.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 UserProperties
    The list of properties of user objects to include in the output. Defaults to DisplayName only.
 
.PARAMETER ServicePrincipalProperties
    The list of properties of service principals (i.e. apps) to include in the output.
    Defaults to DisplayName only.
 
.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-AzureADPSPermission | Export-Csv -Path "permissions.csv" -NoTypeInformation
    Generates a CSV report of all permissions granted to all apps.
 
.EXAMPLE
    PS C:\> Get-AzureADPSPermission -ApplicationPermissions -ShowProgress | Where-Object { $_.Permission -eq "Directory.Read.All" }
    Get all apps which have application permissions for Directory.Read.All.
 
.EXAMPLE
    PS C:\> Get-AzureADPSPermission -UserProperties @("DisplayName", "UserPrincipalName", "Mail") -ServicePrincipalProperties @("DisplayName", "AppId")
    Gets all permissions granted to all apps and includes additional properties for users and service principals.
 
.NOTES
    This function requires Microsoft.Graph PowerShell module and appropriate permissions:
    - Application.Read.All
    - Directory.Read.All
#>

    [CmdletBinding()]
    param(
        [switch] $DelegatedPermissions,
        [switch] $ApplicationPermissions,
        [string[]] $UserProperties = @("DisplayName"),
        [string[]] $ServicePrincipalProperties = @("DisplayName"),
        [switch] $ShowProgress,
        [System.Int32] $PrecacheSize = 999
    )

    # Verify Graph connection
    try {
        $tenant_details = Get-MgOrganization
    }
    catch {
        throw "You must call Connect-MgGraph before running this script."
    }
    Write-Verbose ("TenantId: {0}" -f $tenant_details.Id)

    # Cache objects
    $script:ObjectByObjectId = @{}
    $script:ObjectByObjectType = @{
        'ServicePrincipal' = @{}
        'User' = @{}
    }

    function CacheObject ($Object, $Type) {
        if ($Object) {
            $script:ObjectByObjectType[$Type][$Object.Id] = $Object
            $script:ObjectByObjectId[$Object.Id] = $Object
        }
    }

    function GetObjectByObjectId ($ObjectId) {
        if (-not $script:ObjectByObjectId.ContainsKey($ObjectId)) {
            Write-Verbose ("Querying Graph API for object '{0}'" -f $ObjectId)
            try {
                $object = Get-MgDirectoryObject -DirectoryObjectId $ObjectId
                # Determine type from OdataType
                $type = $object.AdditionalProperties.'@odata.type'.Split('.')[-1]
                CacheObject -Object $object -Type $type
            }
            catch {
                Write-Verbose "Object not found."
            }
        }
        return $script:ObjectByObjectId[$ObjectId]
    }

    # Cache all service principals
    Write-Verbose "Retrieving all ServicePrincipal objects..."
    $servicePrincipals = Get-MgServicePrincipal -All
    foreach($sp in $servicePrincipals) {
        CacheObject -Object $sp -Type 'ServicePrincipal'
    }
    $servicePrincipalCount = $servicePrincipals.Count

    # Cache users
    Write-Verbose ("Retrieving up to {0} User objects..." -f $PrecacheSize)
    $users = Get-MgUser -Top $PrecacheSize
    foreach($user in $users) {
        CacheObject -Object $user -Type 'User'
    }

    if ($DelegatedPermissions -or (-not ($DelegatedPermissions -or $ApplicationPermissions))) {
        Write-Verbose "Retrieving OAuth2PermissionGrants..."
        $oauth2Grants = Get-MgOAuth2PermissionGrant -All

        foreach ($grant in $oauth2Grants) {
            if ($grant.Scope) {
                $grant.Scope.Split(" ") | Where-Object { $_ } | ForEach-Object {
                    $scope = $_

                    $grantDetails = [ordered]@{
                        "PermissionType" = "Delegated"
                        "ClientObjectId" = $grant.ClientId
                        "ResourceObjectId" = $grant.ResourceId
                        "Permission" = $scope
                        "ConsentType" = $grant.ConsentType
                        "PrincipalObjectId" = $grant.PrincipalId
                    }

                    # Add service principal properties
                    if ($ServicePrincipalProperties.Count -gt 0) {
                        $client = $script:ObjectByObjectId[$grant.ClientId]
                        $resource = $script:ObjectByObjectId[$grant.ResourceId]

                        $insertAtClient = 2
                        $insertAtResource = 3
                        foreach ($propertyName in $ServicePrincipalProperties) {
                            $grantDetails.Insert($insertAtClient++, "Client$propertyName", $client.$propertyName)
                            $insertAtResource++
                            $grantDetails.Insert($insertAtResource, "Resource$propertyName", $resource.$propertyName)
                            $insertAtResource++
                        }
                    }

                    # Add user properties
                    if ($UserProperties.Count -gt 0) {
                        $principal = if ($grant.PrincipalId) {
                            $script:ObjectByObjectId[$grant.PrincipalId]
                        } else { @{} }

                        foreach ($propertyName in $UserProperties) {
                            $grantDetails["Principal$propertyName"] = $principal.$propertyName
                        }
                    }

                    New-Object PSObject -Property $grantDetails
                }
            }
        }
    }

    if ($ApplicationPermissions -or (-not ($DelegatedPermissions -or $ApplicationPermissions))) {
        Write-Verbose "Retrieving AppRoleAssignments..."

        $i = 0
        foreach ($sp in $servicePrincipals) {
            if ($ShowProgress) {
                Write-Progress -Activity "Retrieving application permissions..." `
                            -Status ("Checked {0}/{1} apps" -f $i++, $servicePrincipalCount) `
                            -PercentComplete (($i / $servicePrincipalCount) * 100)
            }

            $appRoleAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -All

            foreach ($assignment in $appRoleAssignments) {
                if ($assignment.PrincipalType -eq "ServicePrincipal") {
                    $resource = $script:ObjectByObjectId[$assignment.ResourceId]
                    $appRole = $resource.AppRoles | Where-Object { $_.Id -eq $assignment.AppRoleId }

                    $grantDetails = [ordered]@{
                        "PermissionType" = "Application"
                        "ClientObjectId" = $assignment.PrincipalId
                        "ResourceObjectId" = $assignment.ResourceId
                        "Permission" = $appRole.Value
                    }

                    if ($ServicePrincipalProperties.Count -gt 0) {
                        $client = $script:ObjectByObjectId[$assignment.PrincipalId]

                        $insertAtClient = 2
                        $insertAtResource = 3
                        foreach ($propertyName in $ServicePrincipalProperties) {
                            $grantDetails.Insert($insertAtClient++, "Client$propertyName", $client.$propertyName)
                            $insertAtResource++
                            $grantDetails.Insert($insertAtResource, "Resource$propertyName", $resource.$propertyName)
                            $insertAtResource++
                        }
                    }

                    New-Object PSObject -Property $grantDetails
                }
            }
        }

        if ($ShowProgress) {
            Write-Progress -Completed -Activity "Retrieving application permissions..."
        }
    }
}