internal/functions/Get-AzPolicyAssignments.ps1

function Get-AzPolicyAssignments {
    [CmdletBinding()]
    param (
        $DeployedPolicyResources,
        $PacEnvironment,
        $ScopeTable,
        $SkipRoleAssignments
    )

    $thisPacOwnerId = $PacEnvironment.pacOwnerId
    $desiredState = $PacEnvironment.desiredState
    $excludedPolicyResources = $desiredState.excludedPolicyAssignments
    $environmentTenantId = $PacEnvironment.tenantId
    $rootScopeDetails = $ScopeTable.root
    $excludedScopesTable = $rootScopeDetails.excludedScopesTable

    $query = "PolicyResources | where type == 'microsoft.authorization/policyassignments'"
    $ProgressItemName = "Policy Assignments"
    $policyResources = Search-AzGraphAllItems -Query $query -ProgressItemName $ProgressItemName

    $policyResourcesTable = $DeployedPolicyResources.policyassignments
    $uniquePrincipalIds = @{}
    foreach ($policyResourceRaw in $policyResources) {
        $resourceTenantId = $policyResourceRaw.tenantId
        if ($resourceTenantId -in @($null, "", $environmentTenantId)) {
            $policyResource = Get-ClonedObject $policyResourceRaw -AsHashTable -AsShallowClone
            $id = $policyResource.id
            $testId = $id
            $properties = Get-PolicyResourceProperties $policyResource
            $included = $true
            $resourceIdParts = $null
            $included, $resourceIdParts = Confirm-PolicyResourceExclusions `
                -TestId $testId `
                -ResourceId $id `
                -ScopeTable $ScopeTable `
                -ExcludedScopesTable $excludedScopesTable `
                -ExcludedIds $excludedPolicyResources `
                -PolicyResourceTable $policyResourcesTable
            if ($included) {
                $scope = $resourceIdParts.scope
                $policyResource.resourceIdParts = $resourceIdParts
                $policyResource.scope = $scope
                $policyResource.pacOwner = Confirm-PacOwner -ThisPacOwnerId $thisPacOwnerId -PolicyResource $policyResource -Scope $scope -ManagedByCounters $policyResourcesTable.counters.managedBy
                if ($policyResource.identity -and $policyResource.identity.type -ne "None") {
                    $principalId = ""
                    if ($policyResource.identity.type -eq "SystemAssigned") {
                        $principalId = $policyResource.identity.principalId
                    }
                    else {
                        $userAssignedIdentityId = $policyResource.identity.userAssignedIdentities.PSObject.Properties.Name
                        $principalId = $policyResource.identity.userAssignedIdentities.$userAssignedIdentityId.principalId
                    }
                    $uniquePrincipalIds[$principalId] = $true
                    $policyResourcesTable.counters.withIdentity += 1
                }
                $null = $policyResourcesTable.managed.Add($id, $policyResource)
            }
            else {
                Write-Verbose "Policy Assignment $id excluded"
            }
        }
    }

    if (-not $skipRoleAssignmentsLocal) {
        $DeployedPolicyResources.numberOfPrincipleIds = $uniquePrincipalIds.Count
        $managedRoleAssignmentsByPrincipalId = $DeployedPolicyResources.roleAssignmentsByPrincipalId
        $roleAssignments = [System.Collections.ArrayList]::new()
        $roleDefinitions = [System.Collections.ArrayList]::new()
        $principalIds = '"' + ($uniquePrincipalIds.Keys -join '", "') + '"'
        $ProgressItemName = "Role Assignments"
        if ($PacEnvironment.Cloud -in @("AzureChinaCloud", "AzureUSGovernment")) {
            # if ($PacEnvironment.Cloud -notin @("AzureChinaCloud", "AzureUSGovernment")) {
            # test normal environment
            $roleAssignmentsCount = 0
            $roleAssignmentsLastCount = 0
            $roleAssignmentsProcessed = @{}
            $roleDefinitionsProcessed = @{}
            foreach ($scopeId in $ScopeTable.Keys) {
                $scopeInformation = $ScopeTable.$scopeId
                if ($scopeInformation.type -ne "microsoft.resources/subscriptions/resourceGroups" -and $scopeId -ne "root") {
                    $roleAssignmentsLocal = Get-AzRoleAssignmentsRestMethod -Scope $scopeId -ApiVersion $PacEnvironment.apiVersions.roleAssignments
                    $roleAssignmentsCount += $roleAssignmentsLocal.Count
                    if (($roleAssignmentsLastCount + 10000) -le $roleAssignmentsCount) {
                        Write-Information "Retrieved $roleAssignmentsCount $($ProgressItemName)"
                        $roleAssignmentsLastCount = $roleAssignmentsCount
                    }
                    foreach ($roleAssignment in $roleAssignmentsLocal) {
                        if ($uniquePrincipalIds.ContainsKey($roleAssignment.properties.principalId)) {
                            $id = $roleAssignment.id
                            if (!$roleAssignmentsProcessed.ContainsKey($id)) {
                                $null = $roleAssignmentsProcessed.Add($id, $true)
                                $null = $roleAssignments.Add($roleAssignment)
                            }
                        }
                    }

                }
            }
            if ($roleAssignmentsCount -gt $roleAssignmentsLastCount) {
                Write-Information "Retrieved $roleAssignmentsCount $($ProgressItemName), collected $($roleAssignmentsProcessed.Count) unique $($ProgressItemName)"
            }
            $scopeInformation = $ScopeTable.root
            $scopeId = $scopeInformation.id
            $roleDefinitionsLocal = Get-AzRoleDefinitionsRestMethod -Scope $scopeId -ApiVersion $PacEnvironment.apiVersions.roleAssignments
            $roleDefinitionsCount += $roleDefinitionsLocal.Count
            foreach ($roleDefinition in $roleDefinitionsLocal) {
                $id = $roleDefinition.id
                if ($id.StartsWith("/subscriptions/")) {
                    $possibleScopeId = ($id -split '/', 5)[1..2] -join '/'
                    $subscriptionLevelRoleDefinition = $false
                    foreach ($assignableScope in $roleDefinition.properties.assignableScopes) {
                        if ($assignableScope.StartsWith("$possibleScopeId")) {
                            if (!$roleDefinitionsProcessed.ContainsKey($id)) {
                                $null = $roleDefinitionsProcessed.Add($id, $true)
                                $null = $roleDefinitions.Add($roleDefinition)
                            }
                            $subscriptionLevelRoleDefinition = $true
                            break
                        }
                    }
                    if (-not $subscriptionLevelRoleDefinition) {
                        $id = $id -replace $possibleScopeId, ""
                        $roleDefinition.id = $id
                        if (!$roleDefinitionsProcessed.ContainsKey($id)) {
                            $null = $roleDefinitionsProcessed.Add($id, $true)
                            $null = $roleDefinitions.Add($roleDefinition)
                        }
                    }
                }
                elseif (!$roleDefinitionsProcessed.ContainsKey($id)) {
                    $null = $roleDefinitionsProcessed.Add($id, $true)
                    $null = $roleDefinitions.Add($roleDefinition)
                }
            }
            Write-Information "Retrieved $($roleDefinitionsProcessed.Count) unique Role Definitions"
        }
        else {
            $roleAssignmentsLocal = Search-AzGraphAllItems `
                -Query "authorizationresources | where type == `"microsoft.authorization/roleassignments`" and properties.principalId in ( $principalIds )" `
                -ProgressItemName "Role Assignments" `
                -ProgressIncrement 1000
            $null = $roleAssignments.AddRange($roleAssignmentsLocal)

            $roleDefinitionsLocal = Search-AzGraphAllItems `
                -Query 'authorizationresources | where type == "microsoft.authorization/roledefinitions"' `
                -ProgressItemName "Role Definitions" `
                -ProgressIncrement 1000
            $null = $roleDefinitions.AddRange($roleDefinitionsLocal)
        }
            
        if ($null -ne $PacEnvironment.managingTenantId) {
            foreach ($subscription in $PacEnvironment.managingTenantRootScope) {
                $remoteAssignments = Get-AzRoleAssignmentsRestMethod -Scope $subscription -ApiVersion $PacEnvironment.apiVersions.roleAssignments -Tenant $PacEnvironment.managingTenantId
                foreach ($assignment in $remoteAssignments) {
                    #if the remote assignment is attached to a principal we are looking at then add to the known role assignments object ($roleAssignments)
                    if ($uniquePrincipalIds.ContainsKey($assignment.properties.principalId)) {
                        #Create object with necessary data to normalize
                        $roleAssignmentObj = @{
                            id          = $assignment.id
                            name        = $assignment.name
                            properties  = @{
                                scope            = $assignment.properties.scope
                                principalType    = $assignment.properties.principalType
                                principalId      = $assignment.properties.principalId
                                description      = $assignment.properties.description
                                roleDefinitionId = "/" + ($assignment.properties.roleDefinitionId -split '/', 4)[3] -join '/'
                            }
                            displayName = ""
                            crossTenant = $true
                        }
                        $null = $roleAssignments.Add($roleAssignmentObj)
                        $DeployedPolicyResources.remoteAssignmentsCount += 1
                    }
                }
            }
            Write-Information "Retrieved $($DeployedPolicyResources.remoteAssignmentsCount) remote Role Assignments"  
        }
           
        $roleDefinitionsHt = $DeployedPolicyResources.roleDefinitions
        foreach ($roleDefinition in $roleDefinitions) {
            $roleDefinitionId = $roleDefinition.id
            $roleDefinitionId = $roleDefinition.id
            $roleDefinitionName = $roleDefinition.name
            $roleDefinitionRoleName = $roleDefinition.properties.roleName
            $null = $roleDefinitionsHt.Add($roleDefinitionId, $roleDefinitionRoleName)
            $null = $roleDefinitionsHt.Add($roleDefinitionName, $roleDefinitionRoleName)
        }
            
        # loop through the collected role assignments to collate by principalId
        foreach ($roleAssignment in $roleAssignments) {
            $properties = $roleAssignment.properties
            $principalId = $roleAssignment.properties.principalId
            $roleDefinitionId = $properties.roleDefinitionId
            $roleDefinitionName = ($roleDefinitionId -split '/')[-1]
            $roleDefinitionRoleName = $roleDefinitionName 
            $crossTenant = $roleAssignment.crossTenant
            if ($roleDefinitionsHt.ContainsKey($roleDefinitionId)) {
                $roleDefinitionRoleName = $roleDefinitionsHt.$roleDefinitionId
            }
            elseif ($roleDefinitionId.StartsWith("/subscriptions/")) {
                $subscriptionId = ($roleDefinitionId -split '/')[0..2] -join '/'
                $roleDefinitionId = $roleDefinitionId -replace $subscriptionId, ""
                if ($roleDefinitionsHt.ContainsKey($roleDefinitionId)) {
                    $roleDefinitionRoleName = $roleDefinitionsHt.$roleDefinitionId
                }
                elseif ($roleDefinitionsHt.ContainsKey($roleDefinitionName)) {
                    $roleDefinitionRoleName = $roleDefinitionsHt.$roleDefinitionId
                }
            }
            elseif ($roleDefinitionsHt.ContainsKey($roleDefinitionName)) {
                $roleDefinitionRoleName = $roleDefinitionsHt.$roleDefinitionId
            }
            $normalizedRoleAssignment = @{
                id               = $roleAssignment.id
                name             = $roleAssignment.name
                scope            = $properties.scope
                displayName      = ""
                description      = $properties.description
                objectType       = $properties.principalType
                principalId      = $principalId
                roleDefinitionId = $roleDefinitionId
                roleDisplayName  = $roleDefinitionRoleName
            }            
            if ($crossTenant -eq $true) {
                $normalizedRoleAssignment["crossTenant"] = $true
            }
            $DeployedPolicyResources.numberOfRoleAssignments += 1
            
            $normalizedRoleAssignments = [System.Collections.ArrayList]::new()
            if ($managedRoleAssignmentsByPrincipalId.ContainsKey($principalId)) {
                $normalizedRoleAssignments = $managedRoleAssignmentsByPrincipalId.$principalId
            }
            else {
                $null = $managedRoleAssignmentsByPrincipalId.Add($principalId, $normalizedRoleAssignments)
            }
            $null = $normalizedRoleAssignments.Add($normalizedRoleAssignment)
        }
    }
}