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..."
            }
        }
    }