Scripts/Get-OAuthPermissions.ps1
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 } } } function GetAzureADServicePrincipal ($ObjectId) { Get-AzureADServicePrincipal -ObjectId $ObjectId | ForEach-Object { $Output = $_ $script:homepage = $Output.Homepage $script:PublisherName = $Output.PublisherName $script:ReplyUrls = $Output.ReplyUrls $script:AppDisplayName = $Output.AppDisplayName $script:AppId = $Output.AppId } } function Get-OAuthPermissions { <# .SYNOPSIS Lists delegated permissions (OAuth2PermissionGrants) and application permissions (AppRoleAssignments). Script inspired by: https://gist.github.com/psignoret/41793f8c6211d2df5051d77ca3728c09 .DESCRIPTION Script to list all delegated permissions and application permissions in Azure AD The output will be written to a CSV file. .PARAMETER OutputDir outputDir is the parameter specifying the output directory. Default: Output\OAuthPermissions .PARAMETER ShowProgress Switch parameter to show progress bars during processing. Default: $true .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 .EXAMPLE Get-OAuthPermissions Lists delegated permissions (OAuth2PermissionGrants) and application permissions (AppRoleAssignments). #> [CmdletBinding()] param( [switch] $DelegatedPermissions, [switch] $ApplicationPermissions, [string[]] $UserProperties = @("DisplayName"), [string[]] $ServicePrincipalProperties = @("DisplayName"), [switch] $ShowProgress = $true, [int] $PrecacheSize = 999, [string] $OutputDir = "Output\OAuthPermissions", [string] $Encoding = "UTF8", [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard' ) Set-LogLevel -Level ([LogLevel]::$LogLevel) $isDebugEnabled = $script:LogLevel -eq [LogLevel]::Debug if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] PowerShell Version: $($PSVersionTable.PSVersion)" -Level Debug Write-LogFile -Message "[DEBUG] Input parameters:" -Level Debug Write-LogFile -Message "[DEBUG] DelegatedPermissions: $DelegatedPermissions" -Level Debug Write-LogFile -Message "[DEBUG] ApplicationPermissions: $ApplicationPermissions" -Level Debug Write-LogFile -Message "[DEBUG] UserProperties: $($UserProperties -join ', ')" -Level Debug Write-LogFile -Message "[DEBUG] ServicePrincipalProperties: $($ServicePrincipalProperties -join ', ')" -Level Debug Write-LogFile -Message "[DEBUG] ShowProgress: $ShowProgress" -Level Debug Write-LogFile -Message "[DEBUG] PrecacheSize: $PrecacheSize" -Level Debug Write-LogFile -Message "[DEBUG] OutputDir: '$OutputDir'" -Level Debug Write-LogFile -Message "[DEBUG] Encoding: '$Encoding'" -Level Debug Write-LogFile -Message "[DEBUG] LogLevel: '$LogLevel'" -Level Debug $azureADModule = Get-Module -Name AzureAD -ErrorAction SilentlyContinue if ($azureADModule) { Write-LogFile -Message "[DEBUG] AzureAD Module Version: $($azureADModule.Version)" -Level Debug } else { Write-LogFile -Message "[DEBUG] AzureAD Module not loaded" -Level Debug } } $date = Get-Date -Format "ddMMyyyyHHmmss" $summary = @{ TotalPermissions = 0 DelegatedCount = 0 ApplicationCount = 0 ServicePrincipalsProcessed = 0 StartTime = Get-Date ProcessingTime = $null } try { $tenant_details = Get-AzureADTenantDetail -ErrorAction stop if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Tenant details retrieved successfully:" -Level Debug Write-LogFile -Message "[DEBUG] Tenant ID: $($tenant_details.ObjectId)" -Level Debug Write-LogFile -Message "[DEBUG] Initial Domain: $(($tenant_details.VerifiedDomains | Where-Object { $_.Initial }).Name)" -Level Debug Write-LogFile -Message "[DEBUG] Display Name: $($tenant_details.DisplayName)" -Level Debug } } catch { write-logFile -Message "[INFO] Ensure you are connected to Azure by running the Connect-Azure command before executing this script" -Color "Yellow" -Level Minimal Write-logFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" -Level Minimal if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Connection error details:" -Level Debug Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug } throw } Write-LogFile -Message "=== Starting OAuth Permissions Collection ===" -Color "Cyan" -Level Standard if (!(test-path $OutputDir)) { New-Item -ItemType Directory -Force -Name $OutputDir > $null } else { if (!(Test-Path -Path $OutputDir)) { Write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop Write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" -Level Minimal } } $report = @( Write-Verbose ("TenantId: {0}, InitialDomain: {1}" -f ` $tenant_details.ObjectId, ` ($tenant_details.VerifiedDomains | Where-Object { $_.Initial }).Name) $script:ObjectByObjectId = @{} $script:ObjectByObjectClassId = @{} $empty = @{} # Write-LogFile -Message "[INFO] Retrieving all ServicePrincipal objects..." -Level Standard Get-AzureADServicePrincipal -All $true | ForEach-Object { CacheObject -Object $_ $summary.ServicePrincipalsProcessed++ } $servicePrincipalCount = $script:ObjectByObjectClassId['ServicePrincipal'].Count if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] ServicePrincipal retrieval completed:" -Level Debug Write-LogFile -Message "[DEBUG] Total ServicePrincipals cached: $servicePrincipalCount" -Level Debug Write-LogFile -Message "[DEBUG] Cache structure initialized with $($script:ObjectByObjectClassId.Keys.Count) object types" -Level Debug } if ($DelegatedPermissions -or (-not ($DelegatedPermissions -or $ApplicationPermissions))) { if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Processing delegated permissions..." -Level Debug Write-LogFile -Message "[DEBUG] Pre-caching $PrecacheSize users..." -Level Debug } Get-AzureADUser -Top $PrecacheSize | Where-Object { CacheObject -Object $_ } $fastQueryMode = $false try { $null = Get-AzureADOAuth2PermissionGrant -Top 999 $fastQueryMode = $true } catch { if ($_.Exception.Message -and $_.Exception.Message.StartsWith("Unexpected end when deserializing array.")) { Write-LogFile -Message "[ERROR] Fast query for delegated permissions failed, using slow method" -Level Minimal -Color "Red" } else { throw $_ } } $delegatedGrantCount = 0 GetOAuth2PermissionGrants -FastMode:$fastQueryMode | ForEach-Object { $grant = $_ GetAzureADServicePrincipal($grant.ClientId) if ($grant.Scope) { $grant.Scope.Split(" ") | Where-Object { $_ } | ForEach-Object { $summary.DelegatedCount++ if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Processing permission: '$_' for app '$($script:AppDisplayName)'" -Level Debug } $grantDetails = [ordered]@{ "PermissionType" = "Delegated" "AppId" = $script:AppId "ClientObjectId" = $grant.ClientId "ResourceObjectId" = $grant.ResourceId "Permission" = $_ "ConsentType" = $grant.ConsentType "PrincipalObjectId" = $grant.PrincipalId "Homepage" = $script:homepage "PublisherName" = $script:PublisherName "ReplyUrls" = $null "ExpiryTime" = $grant.ExpiryTime } if ($null -ne $ReplyUrls) { $grantDetails["ReplyUrls"] = $script:ReplyUrls -join ', ' } if ($ServicePrincipalProperties.Count -gt 0) { $client = GetObjectByObjectId -ObjectId $grant.ClientId $resource = GetObjectByObjectId -ObjectId $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 ++ } } if ($UserProperties.Count -gt 0) { $principal = $empty if ($grant.PrincipalId) { $principal = GetObjectByObjectId -ObjectId $grant.PrincipalId } foreach ($propertyName in $UserProperties) { $grantDetails["Principal$propertyName"] = $principal.$propertyName } } New-Object PSObject -Property $grantDetails } } } if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Delegated permissions processing completed:" -Level Debug Write-LogFile -Message "[DEBUG] Total grants processed: $delegatedGrantCount" -Level Debug Write-LogFile -Message "[DEBUG] Total delegated permissions: $($summary.DelegatedCount)" -Level Debug } } if ($ApplicationPermissions -or (-not ($DelegatedPermissions -or $ApplicationPermissions))) { $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 GetAzureADServicePrincipal($sp.ObjectId) Get-AzureADServiceAppRoleAssignedTo -ObjectId $sp.ObjectId -All $true ` | Where-Object { $_.PrincipalType -eq "ServicePrincipal" } | ForEach-Object { $summary.ApplicationCount++ $assignment = $_ $resource = GetObjectByObjectId -ObjectId $assignment.ResourceId $appRole = $resource.AppRoles | Where-Object { $_.Id -eq $assignment.Id } if ($isDebugEnabled -and $null -eq $appRole) { Write-LogFile -Message "[DEBUG] WARNING: Could not find app role with ID $($assignment.Id) for resource $($assignment.ResourceId)" -Level Debug } $grantDetails = [ordered]@{ "PermissionType" = "Application" "AppId" = $null "ClientObjectId" = $assignment.PrincipalId "ResourceObjectId" = $assignment.ResourceId "Permission" = $appRole.Value "IsEnabled" = $null "Description" = $null "CreationTimestamp" = $null "Homepage" = $script:homepage "PublisherName" = $script:PublisherName "ReplyUrls" = $null } if ($null -ne $sp -and $sp.AppId) { $grantDetails["AppId"] = $sp.AppId } if ($null -ne $ReplyUrls) { $grantDetails["ReplyUrls"] = $script:ReplyUrls -join ', ' } if ($null -ne $appRole -and $appRole.IsEnabled) { $grantDetails["IsEnabled"] = $appRole.IsEnabled } if ($null -ne $appRole -and $appRole.Description) { $grantDetails["Description"] = $appRole.Description } if ($null -ne $assignment -and $assignment.CreationTimestamp) { $grantDetails["CreationTimestamp"] = $assignment.CreationTimestamp } if ($ServicePrincipalProperties.Count -gt 0) { $client = GetObjectByObjectId -ObjectId $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 } } } ) $summary.TotalPermissions = $summary.DelegatedCount + $summary.ApplicationCount $summary.ProcessingTime = (Get-Date) - $summary.StartTime $report | ConvertTo-Csv | Format-Table > $null $prop = $report.ForEach{ $_.PSObject.Properties.Name } | Select-Object -Unique $report | Select-Object $prop | Export-CSV -NoTypeInformation -Path "$OutputDir\$($date)-OAuthPermissions.csv" -Encoding $Encoding Write-LogFile -Message "`n=== OAuth Permissions Analysis Summary ===" -Color "Cyan" -Level Standard Write-LogFile -Message "Service Principals Processed: $($summary.ServicePrincipalsProcessed)" -Level Standard Write-LogFile -Message "Total Permissions Found: $($summary.TotalPermissions)" -Level Standard Write-LogFile -Message " - Delegated Permissions: $($summary.DelegatedCount)" -Level Standard Write-LogFile -Message " - Application Permissions: $($summary.ApplicationCount)" -Level Standard Write-LogFile -Message "`nOutput File: $OutputDir\$($date)-OAuthPermissions.csv" -Level Standard Write-LogFile -Message "Processing Time: $($summary.ProcessingTime.ToString('mm\:ss'))" -Color "Green" -Level Standard Write-LogFile -Message "===================================" -Color "Cyan" -Level Standard } 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 = "Output\OAuthPermissions", [string] $Encoding = "UTF8", [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard' ) Set-LogLevel -Level ([LogLevel]::$LogLevel) $isDebugEnabled = $script:LogLevel -eq [LogLevel]::Debug if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] PowerShell Version: $($PSVersionTable.PSVersion)" -Level Debug Write-LogFile -Message "[DEBUG] Input parameters:" -Level Debug Write-LogFile -Message "[DEBUG] DelegatedPermissions: $DelegatedPermissions" -Level Debug Write-LogFile -Message "[DEBUG] ApplicationPermissions: $ApplicationPermissions" -Level Debug Write-LogFile -Message "[DEBUG] OutputDir: '$OutputDir'" -Level Debug Write-LogFile -Message "[DEBUG] Encoding: '$Encoding'" -Level Debug Write-LogFile -Message "[DEBUG] LogLevel: '$LogLevel'" -Level Debug $graphModules = Get-Module -Name Microsoft.Graph* -ErrorAction SilentlyContinue if ($graphModules) { Write-LogFile -Message "[DEBUG] Microsoft Graph Modules loaded:" -Level Debug foreach ($module in $graphModules) { Write-LogFile -Message "[DEBUG] - $($module.Name) v$($module.Version)" -Level Debug } } else { Write-LogFile -Message "[DEBUG] No Microsoft Graph modules loaded" -Level Debug } } $date = Get-Date -Format "ddMMyyyyHHmmss" $summary = @{ TotalPermissions = 0 DelegatedCount = 0 ApplicationCount = 0 ServicePrincipalsProcessed = 0 DelegatedGrantsProcessed = 0 StartTime = Get-Date ProcessingTime = $null } $requiredScopes = @("Directory.Read.All", "Application.Read.All") $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Graph authentication details:" -Level Debug Write-LogFile -Message "[DEBUG] Required scopes: $($requiredScopes -join ', ')" -Level Debug Write-LogFile -Message "[DEBUG] Authentication type: $($graphAuth.AuthType)" -Level Debug Write-LogFile -Message "[DEBUG] Current scopes: $($graphAuth.Scopes -join ', ')" -Level Debug if ($graphAuth.MissingScopes.Count -gt 0) { Write-LogFile -Message "[DEBUG] Missing scopes: $($graphAuth.MissingScopes -join ', ')" -Level Debug } else { Write-LogFile -Message "[DEBUG] Missing scopes: None" -Level Debug } } Write-LogFile -Message "=== Starting OAuth Permissions Collection ===" -Color "Cyan" -Level Standard if (!(Test-Path $OutputDir)) { New-Item -ItemType Directory -Force -Path $OutputDir > $null if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Created output directory: $OutputDir" -Level Debug } } else { if (!(Test-Path -Path $OutputDir)) { Write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop Write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" -Level Minimal } else { if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Using existing output directory: $OutputDir" -Level Debug } } } $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 $summary.TotalPermissions = $summary.DelegatedCount + $summary.ApplicationCount $summary.ProcessingTime = (Get-Date) - $summary.StartTime Write-LogFile -Message "[INFO] Exporting results to CSV..." -Level Standard if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Exporting results to CSV..." -Level Debug Write-LogFile -Message "[DEBUG] Total records to export: $($report.Count)" -Level Debug } $outputPath = Join-Path $OutputDir "$($date)-OAuthPermissions.csv" $report | Export-CSV -NoTypeInformation -Path $outputPath -Encoding $Encoding Write-LogFile -Message "[INFO] Export completed successfully" -Level Standard -Color "Green" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Export completed successfully" -Level Debug Write-LogFile -Message "[DEBUG] Output file: $outputPath" -Level Debug Write-LogFile -Message "[DEBUG] File size: $(if (Test-Path $outputPath) { (Get-Item $outputPath).Length } else { 'File not found' }) bytes" -Level Debug Write-LogFile -Message "[DEBUG] Performance metrics:" -Level Debug Write-LogFile -Message "[DEBUG] Processing time: $($summary.ProcessingTime.ToString('mm\:ss\.fff'))" -Level Debug Write-LogFile -Message "[DEBUG] Records per second: $([math]::Round($summary.TotalPermissions / $summary.ProcessingTime.TotalSeconds, 2))" -Level Debug } Write-LogFile -Message "`n=== OAuth Permissions Analysis Summary ===" -Color "Cyan" -Level Standard Write-LogFile -Message "Service Principals Processed: $($summary.ServicePrincipalsProcessed)" -Level Standard Write-LogFile -Message "Delegated Grants Processed: $($summary.DelegatedGrantsProcessed)" -Level Standard Write-LogFile -Message "Total Permissions Found: $($summary.TotalPermissions)" -Level Standard Write-LogFile -Message " - Delegated Permissions: $($summary.DelegatedCount)" -Level Standard Write-LogFile -Message " - Application Permissions: $($summary.ApplicationCount)" -Level Standard Write-LogFile -Message "`nOutput File: $outputPath" -Level Standard Write-LogFile -Message "Processing Time: $($summary.ProcessingTime.ToString('mm\:ss'))" -Color "Green" -Level Standard Write-LogFile -Message "===================================" -Color "Cyan" -Level Standard } |