Scripts/Get-OAuthPermissions.ps1

function Get-OAuthPermissionsGraph {
    <#
    .SYNOPSIS
    Lists delegated permissions (OAuth2PermissionGrants) and application permissions (AppRoleAssignments) using Microsoft Graph API.
     
    .DESCRIPTION
    Script to list all delegated permissions and application permissions in Azure AD using Microsoft Graph API
    The output will be written to a CSV file.
     
    .PARAMETER OutputDir
    outputDir is the parameter specifying the output directory.
    Default: Output\OAuthPermissions
     
    .PARAMETER Encoding
    Encoding is the parameter specifying the encoding of the CSV output file.
    Default: UTF8
     
    .PARAMETER LogLevel
    Specifies the level of logging:
    None: No logging
    Minimal: Critical errors only
    Standard: Normal operational logging
    Debug: Verbose logging for debugging purposes
    Default: Standard
    #>

    
    [CmdletBinding()]
    param(
        [switch] $DelegatedPermissions,
        [switch] $ApplicationPermissions,
        [string] $OutputDir,
        [string] $Encoding = "UTF8",
        [ValidateSet('None', 'Minimal', 'Standard', 'Debug')]
        [string]$LogLevel = 'Standard'
    )

    Init-Logging
    Write-LogFile -Message "=== Starting OAuth Permissions Collection ===" -Color "Cyan" -Level Standard
    
    Init-OutputDir -Component "EntraID" -SubComponent "OAuthPermissions" -FilePostfix "OAuthPermissions" -CustomOutputDir $OutputDir

    $requiredScopes = @("Application.Read.All")
    Check-GraphContext -RequiredScopes $requiredScopes

    $summary = [ordered]@{
        ServicePrincipalsProcessed = 0
        DelegatedGrantsProcessed = 0
        TotalPermissions = 0
        DelegatedCount = 0
        ApplicationCount = 0

    }

    $script:ObjectCache = @{}
    function Get-CachedObject {
        param($Id, $Type)
        
        if (-not $script:ObjectCache.ContainsKey($Id)) {
            try {
                if ($isDebugEnabled) {
                    Write-LogFile -Message "[DEBUG] Fetching $Type with ID: $Id" -Level Debug
                }
                $object = switch ($Type) {
                    'ServicePrincipal' { Get-MgServicePrincipal -ServicePrincipalId $Id }
                    'User' { Get-MgUser -UserId $Id }
                    'Application' { Get-MgApplication -ApplicationId $Id }
                }
                $script:ObjectCache[$Id] = $object
                if ($isDebugEnabled) {
                    Write-LogFile -Message "[DEBUG] Successfully cached $Type : $($object.DisplayName)" -Level Debug
                }
            }
            catch {
                Write-Verbose "Could not retrieve object $Id : $_"
                if ($isDebugEnabled) {
                    Write-LogFile -Message "[DEBUG] Failed to retrieve $Type $Id : $($_.Exception.Message)" -Level Debug
                }
                return $null
            }
        } else {
            if ($isDebugEnabled) {
                Write-LogFile -Message "[DEBUG] Using cached $Type with ID: $Id" -Level Debug
            }
        }
        return $script:ObjectCache[$Id]
    }

    $report = @()
    Write-LogFile -Message "[INFO] Retrieving all ServicePrincipal objects..." -Level Standard
    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] Starting ServicePrincipal retrieval via Microsoft Graph..." -Level Debug
    }

    $allServicePrincipals = Get-MgServicePrincipal -All
    $servicePrincipalCount = $allServicePrincipals.Count
    $summary.ServicePrincipalsProcessed = $servicePrincipalCount

    Write-LogFile -Message "[INFO] Successfully retrieved $servicePrincipalCount ServicePrincipal objects" -Level Standard
    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] ServicePrincipal retrieval completed:" -Level Debug
        Write-LogFile -Message "[DEBUG] Total ServicePrincipals retrieved: $servicePrincipalCount" -Level Debug
        Write-LogFile -Message "[DEBUG] Pre-caching all service principals..." -Level Debug
    }

    Write-LogFile -Message "[INFO] Caching all ServicePrincipal objects..." -Level Standard
    $cachingCounter = 0
    foreach ($sp in $allServicePrincipals) {
        $script:ObjectCache[$sp.Id] = $sp
        $cachingCounter++
        
        # Log progress every 100 service principals
        if ($cachingCounter % 100 -eq 0) {
            Write-LogFile -Message "[INFO] Cached $cachingCounter of $servicePrincipalCount service principals..." -Level Standard
        }
        
        if ($isDebugEnabled -and ($cachingCounter % 50 -eq 0)) {
            Write-LogFile -Message "[DEBUG] Cached ServicePrincipal: $($sp.DisplayName) (Total: $cachingCounter)" -Level Debug
        }
    }

    Write-LogFile -Message "[INFO] All ServicePrincipal objects cached successfully" -Level Standard -Color "Green"

    if ($DelegatedPermissions -or (-not ($DelegatedPermissions -or $ApplicationPermissions))) {
        Write-LogFile -Message "[INFO] Processing delegated permissions..." -Level Standard
        
        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Retrieving OAuth2PermissionGrants via Microsoft Graph..." -Level Debug
        }

        $allDelegatedGrants = Get-MgOauth2PermissionGrant -All
        Write-LogFile -Message "[INFO] Found $($allDelegatedGrants.Count) OAuth2PermissionGrants to process" -Level Standard

        $grantCounter = 0
        foreach ($grant in $allDelegatedGrants) {
            $grantCounter++
            $summary.DelegatedGrantsProcessed++
            
            $clientSp = Get-CachedObject -Id $grant.ClientId -Type 'ServicePrincipal'
            $resourceSp = Get-CachedObject -Id $grant.ResourceId -Type 'ServicePrincipal'

            # Log progress every 25 grants
            if ($grantCounter % 25 -eq 0) {
                Write-LogFile -Message "[INFO] Processing delegated grant $grantCounter of $($allDelegatedGrants.Count) - App: '$($clientSp.DisplayName)'" -Level Standard
            }
            
            if ($isDebugEnabled -and ($grantCounter % 10 -eq 0)) {
                Write-LogFile -Message "[DEBUG] Processing grant $grantCounter of $($allDelegatedGrants.Count) for app '$($clientSp.DisplayName)'" -Level Debug
            }

            if ($grant.Scope) {
                $scopes = $grant.Scope.Split(' ')
                if ($isDebugEnabled) {
                    Write-LogFile -Message "[INFO] Grant for '$($clientSp.DisplayName)' has $($scopes.Count) permissions: $($scopes -join ', ')" -Level Debug
                }
                if ($isDebugEnabled -and $scopes.Count -gt 5) {
                    Write-LogFile -Message "[DEBUG] Grant has $($scopes.Count) scopes: $($scopes -join ', ')" -Level Debug
                }

                foreach ($scope in $scopes) {
                    if ($scope) {
                        $summary.DelegatedCount++

                        if ($isDebugEnabled -and ($summary.DelegatedCount % 25 -eq 0)) {
                            Write-LogFile -Message "[DEBUG] Processing permission: '$scope' for app '$($clientSp.DisplayName)' (Permission $($summary.DelegatedCount))" -Level Debug
                        }
                        
                        $principalDisplayName = if ($grant.PrincipalId) {
                            $principal = Get-CachedObject -Id $grant.PrincipalId -Type 'User'
                            $principal.DisplayName
                        } else { "" }

                        $publisherName = if ($clientSp.PublisherName) {
                            $clientSp.PublisherName
                        } else {
                            if ($clientSp.DisplayName -like "Microsoft*") { "Microsoft" } else { "" }
                        }

                        $AccountEnabled = $clientSp.AccountEnabled
                        if ($AccountEnabled -eq $true) {
                        $ApplicationStatus = "Enabled"
                        }
                        else {
                            $ApplicationStatus = "Disabled"
                        }

                        $Tags = $clientSp.Tags
                        if ($Tags -Contains "HideApp") {
                            $ApplicationVisibility = "Hidden"
                        }
                        else {
                            $ApplicationVisibility = "Visible"
                        }

                        if ($Tags -Contains "WindowsAzureActiveDirectoryOnPremApp") {
                            $IsAppProxy = "Yes"
                        }
                        else {
                            $IsAppProxy = "No"
                        }

                        if ($clientSp.AppRoleAssignmentRequired -eq $false) {
                            $AssignmentRequired = "No"
                        }
                        else {
                            $AssignmentRequired = "Yes"
                        }

                        $ServicePrincipalTypes = @()
                        if ($clientSp.AppOwnerOrganizationId -eq "f8cdef31-a31e-4b4a-93e4-5f571e91255a" -or $clientSp.AppOwnerOrganizationId -eq "72f988bf-86f1-41af-91ab-2d7cd011db47") { $ServicePrincipalTypes += "Microsoft Application" }
                        if ($clientSp.ServicePrincipalType -eq "ManagedIdentity") { $ServicePrincipalTypes += "Managed Identity" }
                        if ($clientSp.Tags -contains "WindowsAzureActiveDirectoryIntegratedApp") { $ServicePrincipalTypes += "Enterprise Application" }
                        $ApplicationType = $ServicePrincipalTypes -join " & "
                        
                        $grantDetails = [ordered]@{
                            "PermissionType"         = "Delegated"
                            "AppId"                  = $clientSp.AppId
                            "ClientObjectId"         = $grant.ClientId
                            "AppDisplayName"           = $clientSp.DisplayName
                            "ResourceObjectId"       = $grant.ResourceId
                            "ResourceDisplayName"    = $resourceSp.DisplayName
                            "Permission"             = $scope
                            "ConsentType"            = $grant.ConsentType
                            "PrincipalObjectId"      = $grant.PrincipalId
                            "PrincipalDisplayName"   = $principalDisplayName
                            "Homepage"               = $clientSp.Homepage
                            "PublisherName"          = $publisherName
                            "ReplyUrls"              = ($clientSp.ReplyUrls -join ', ')
                            "ExpiryTime"             = $grant.ExpiryTime
                            "CreatedDateTime" = if ($clientSp.AdditionalProperties.ContainsKey('createdDateTime')) {
                                $clientSp.AdditionalProperties['createdDateTime']
                            } else {
                                $null
                            }
                            "AppOwnerOrganizationId" = $clientSp.AppOwnerOrganizationId
                            "ApplicationStatus"      = $ApplicationStatus
                            "ApplicationVisibility"  = $ApplicationVisibility 
                            "AssignmentRequired"     = $AssignmentRequired 
                            "IsAppProxy"             = $IsAppProxy 
                            "PublisherDisplayName"   = $clientSp.VerifiedPublisher.DisplayName
                            "VerifiedPublisherId"    = $clientSp.VerifiedPublisher.VerifiedPublisherId
                            "AddedDateTime"          = $clientSp.VerifiedPublisher.AddedDateTime 
                            "SignInAudience"         = $clientSp.SignInAudience 
                            "ApplicationType"        = $ApplicationType 
                        }

                        $report += [PSCustomObject]$grantDetails
                    }
                }
            }
        }

        Write-LogFile -Message "[INFO] Delegated permissions processing completed - Found $($summary.DelegatedCount) delegated permissions from $($summary.DelegatedGrantsProcessed) grants" -Level Standard -Color "Green"
        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Delegated permissions processing completed:" -Level Debug
            Write-LogFile -Message "[DEBUG] Total grants processed: $($summary.DelegatedGrantsProcessed)" -Level Debug
            Write-LogFile -Message "[DEBUG] Total delegated permissions: $($summary.DelegatedCount)" -Level Debug
        }
    }

    if ($ApplicationPermissions -or (-not ($DelegatedPermissions -or $ApplicationPermissions))) {
        Write-LogFile -Message "[INFO] Processing application permissions..." -Level Standard
        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Processing application permissions..." -Level Debug
        }
        
        $appCounter = 0
        foreach ($sp in $allServicePrincipals) {
            $appCounter++
            
            # Log progress every 25 apps
            if ($appCounter % 25 -eq 0) {
                Write-LogFile -Message "[INFO] Processing application permissions for app $appCounter of $servicePrincipalCount - '$($sp.DisplayName)'" -Level Standard
            }
            
            if ($ShowProgress) {
                Write-Progress -Activity "Retrieving application permissions..." `
                    -Status ("Checked {0}/{1} apps" -f $appCounter, $servicePrincipalCount) `
                    -PercentComplete (($appCounter / $servicePrincipalCount) * 100)
            }

            if ($isDebugEnabled -and ($appCounter % 50 -eq 0)) {
                Write-LogFile -Message "[DEBUG] Processing application permissions for app $appCounter of $servicePrincipalCount - '$($sp.DisplayName)'" -Level Debug
            }

            $appRoleAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -All
            
            if ($isDebugEnabled -and $appRoleAssignments.Count -gt 0) {
                Write-LogFile -Message "[DEBUG] Found $($appRoleAssignments.Count) app role assignments for $($sp.DisplayName)" -Level Debug
            }

            foreach ($assignment in $appRoleAssignments) {
                $summary.ApplicationCount++
                
                $resourceSp = Get-CachedObject -Id $assignment.ResourceId -Type 'ServicePrincipal'
                $appRole = $resourceSp.AppRoles | Where-Object { $_.Id -eq $assignment.AppRoleId }
                if ($isDebugEnabled) {
                    Write-LogFile -Message "[INFO] Application permission: '$($appRole.Value)' for app '$($sp.DisplayName)' on resource '$($resourceSp.DisplayName)'" -Level Debug
                }

                if ($isDebugEnabled -and $null -eq $appRole) {
                    Write-LogFile -Message "[DEBUG] WARNING: Could not find app role with ID $($assignment.AppRoleId) for resource $($assignment.ResourceId)" -Level Debug
                }

                $publisherName = if ($sp.PublisherName) {
                    $sp.PublisherName
                } else {
                    if ($sp.DisplayName -like "Microsoft*") { "Microsoft" } else { "" }
                }

                $AccountEnabled = $sp.AccountEnabled
                if ($AccountEnabled -eq "True") {
                    $ApplicationStatus = "Enabled"
                }
                else {
                    $ApplicationStatus = "Disabled"
                }

                $Tags = $sp.Tags
                if ($Tags -Contains "HideApp") {
                    $ApplicationVisibility = "Hidden"
                }
                else {
                    $ApplicationVisibility = "Visible"
                }

                if ($Tags -Contains "WindowsAzureActiveDirectoryOnPremApp") {
                    $IsAppProxy = "Yes"
                }
                else {
                    $IsAppProxy = "No"
                }

                if ($sp.AppRoleAssignmentRequired -eq $false) {
                    $AssignmentRequired = "No"
                }
                else {
                    $AssignmentRequired = "Yes"
                }

                $ServicePrincipalTypes = @()
                if ($sp.AppOwnerOrganizationId -eq "f8cdef31-a31e-4b4a-93e4-5f571e91255a" -or $sp.AppOwnerOrganizationId -eq "72f988bf-86f1-41af-91ab-2d7cd011db47") { $ServicePrincipalTypes += "Microsoft Application" }
                if ($sp.ServicePrincipalType -eq "ManagedIdentity") { $ServicePrincipalTypes += "Managed Identity" }
                if ($sp.Tags -contains "WindowsAzureActiveDirectoryIntegratedApp") { $ServicePrincipalTypes += "Enterprise Application" }
                $ApplicationType = $ServicePrincipalTypes -join " & "

                $grantDetails = [ordered]@{
                    "PermissionType"         = "Application"
                    "AppId"                  = $sp.AppId
                    "ClientObjectId"         = $assignment.PrincipalId
                    "AppDisplayName"           = $sp.DisplayName
                    "ResourceObjectId"       = $assignment.ResourceId
                    "ResourceDisplayName"    = $resourceSp.DisplayName
                    "Permission"             = $appRole.Value
                    "ConsentType"            = "AllPrincipals"
                    "PrincipalObjectId"      = $null
                    "PrincipalDisplayName"   = ""
                    "Homepage"               = $sp.Homepage
                    "PublisherName"          = $publisherName
                    "ReplyUrls"              = ($sp.ReplyUrls -join ', ')
                    "IsEnabled"              = $appRole.IsEnabled
                    "Description"            = $appRole.Description
                    "CreationTimestamp"      = $assignment.CreatedDateTime
                    "CreatedDateTime"        = $sp.AdditionalProperties.createdDateTime
                    "AppOwnerOrganizationId" = $sp.AppOwnerOrganizationId
                    "ApplicationStatus"      = $ApplicationStatus
                    "ApplicationVisibility"  = $ApplicationVisibility 
                    "AssignmentRequired"     = $AssignmentRequired
                    "IsAppProxy"             = $IsAppProxy
                    "PublisherDisplayName"   = $sp.VerifiedPublisher.DisplayName
                    "VerifiedPublisherId"    = $sp.VerifiedPublisher.VerifiedPublisherId
                    "AddedDateTime"          = $sp.VerifiedPublisher.AddedDateTime
                    "SignInAudience"         = $sp.SignInAudience 
                    "ApplicationType"        = $ApplicationType
                }

                $report += [PSCustomObject]$grantDetails
            }
        }

        Write-LogFile -Message "[INFO] Application permissions processing completed - Found $($summary.ApplicationCount) application permissions" -Level Standard -Color "Green"
        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Application permissions processing completed:" -Level Debug
            Write-LogFile -Message "[DEBUG] Total application permissions: $($summary.ApplicationCount)" -Level Debug
        }
    }

    # Export results
    Write-LogFile -Message "[INFO] Exporting results to CSV..." -Level Standard
    $report | Export-Csv -Path $script:outputFile -NoTypeInformation -Encoding $Encoding
    $oauthPermissionsFile = $script:outputFile

    Write-LogFile -Message "[INFO] Exporting service principals to CSV..." -Level Standard
    Init-OutputDir -Component "EntraID" -SubComponent "ServicePrincipals" -FilePostfix "ServicePrincipals" -CustomOutputDir $OutputDir
    $allServicePrincipals | Select-Object AppId, AppDisplayName, AppDescription, AccountEnabled, AppOwnerOrganizationId | Export-Csv -Path $script:outputFile -NoTypeInformation -Encoding $Encoding
    $servicePrincipalsFile = $script:outputFile

    Write-LogFile -Message "[INFO] Exporting App Registrations to CSV..." -Level Standard
    if ($OutputDir) {
       Init-OutputDir -Component "EntraID" -SubComponent "AppRegistrations" -FilePostfix "AppRegistrations" -CustomOutputDir $OutputDir
    } else {
       Init-OutputDir -Component "EntraID" -SubComponent "AppRegistrations" -FilePostfix "AppRegistrations"
    }

     Get-MgApplication -All | Select-Object Id, DisplayName, AppId, CreatedDateTime | Export-Csv -Path $script:outputFile -NoTypeInformation -Encoding $Encoding
    $appRegistrationsFile = $script:outputFile

    $summary.TotalPermissions = $summary.DelegatedCount + $summary.ApplicationCount
    $summaryOutput = [ordered]@{
        "Processing Summary" = [ordered]@{
            "Service Principals Processed" = $summary.ServicePrincipalsProcessed
            "Delegated Grants Processed" = $summary.DelegatedGrantsProcessed
        }
        "Permissions Found" = [ordered]@{
            "Total Permissions" = $summary.TotalPermissions
            "Delegated Permissions" = $summary.DelegatedCount
            "Application Permissions" = $summary.ApplicationCount
        }
        "Output Files" = [ordered]@{
            "OAuth Permissions" = $oauthPermissionsFile
            "Service Principals" = $servicePrincipalsFile
            "App Registrations" = $appRegistrationsFile
        }
    }

    Write-Summary -Summary $summaryOutput -Title "OAuth Permissions Summary" -SkipExportDetails
}