Private/22-GetAtlasDirectoryRoles.ps1

function Get-AtlasDirectoryRole {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $TenantId,

        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [AtlasNode[]] $KnownNode
    )

    $result = [AtlasCollectionResult]::new()
    $keyById = @{}
    $nodeById = @{}
    foreach ($node in $KnownNode) {
        if (-not $keyById.ContainsKey($node.Id) -or $node.Status -eq 'complete') {
            $keyById[$node.Id] = $node.Key
            $nodeById[$node.Id] = $node
        }
    }

    $definitionsEndpoint = '/v1.0/roleManagement/directory/roleDefinitions?$select=id,displayName,description,isBuiltIn,isEnabled'
    $assignmentsEndpoint = '/v1.0/roleManagement/directory/roleAssignments?$select=id,principalId,roleDefinitionId,directoryScopeId'
    $eligibilityEndpoint = '/v1.0/roleManagement/directory/roleEligibilityScheduleInstances?$select=id,principalId,roleDefinitionId,directoryScopeId,startDateTime,endDateTime,memberType'
    $definitionResponse = Invoke-AtlasGraphRequest -Uri $definitionsEndpoint
    $assignmentResponse = Invoke-AtlasGraphRequest -Uri $assignmentsEndpoint
    try {
        $eligibilityResponse = Invoke-AtlasGraphRequest -Uri $eligibilityEndpoint
    }
    catch {
        $result.Status = 'partial'
        $result.Warnings.Add("PIM eligible role assignments could not be collected: $(Get-AtlasSafeErrorDetail -ErrorRecord $_)")
        $eligibilityResponse = [pscustomobject] @{
            Items = @()
            Metrics = @{ requestCount = 0; retryCount = 0 }
        }
    }

    $roleKeyById = @{}
    foreach ($definition in $definitionResponse.Items) {
        $roleNode = New-AtlasNode -TenantId $TenantId -Id $definition.id -Kind 'roleDefinition' -DisplayName $definition.displayName -Properties @{
            description = $definition.description
            isBuiltIn = $definition.isBuiltIn
            isEnabled = $definition.isEnabled
        } -Source @{
            provider = 'microsoftGraph'
            apiVersion = 'v1.0'
            odataType = '#microsoft.graph.unifiedRoleDefinition'
            resourcePath = "/roleManagement/directory/roleDefinitions/$($definition.id)"
            collector = 'directoryRoles'
        }
        $result.Nodes.Add($roleNode)
        $roleKeyById[$definition.id] = $roleNode.Key
    }

    foreach ($assignment in $assignmentResponse.Items) {
        $appScopeId = Get-AtlasResponseProperty -InputObject $assignment -Name 'appScopeId'
        if (-not $roleKeyById.ContainsKey($assignment.roleDefinitionId)) {
            $result.Status = 'partial'
            $result.Warnings.Add("Role definition '$($assignment.roleDefinitionId)' was not returned.")
            continue
        }

        if ($keyById.ContainsKey($assignment.principalId)) {
            $principalKey = $keyById[$assignment.principalId]
        }
        else {
            $principalNode = New-AtlasNode -TenantId $TenantId -Id $assignment.principalId -Kind 'directoryObject' -DisplayName $assignment.principalId -Status 'unresolved' -Source @{
                provider = 'microsoftGraph'
                apiVersion = 'v1.0'
                resourcePath = "/directoryObjects/$($assignment.principalId)"
                collector = 'directoryRoles'
            }
            $result.Nodes.Add($principalNode)
            $principalKey = $principalNode.Key
        }

        $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'directoryRoles' -Endpoint $assignmentsEndpoint -SourceObjectId $assignment.id -Fields @{
            principalId = $assignment.principalId
            roleDefinitionId = $assignment.roleDefinitionId
            directoryScopeId = $assignment.directoryScopeId
            appScopeId = $appScopeId
            activation = 'active'
        }
        $result.Evidence.Add($evidence)
        $result.Edges.Add(
            (New-AtlasEdge -TenantId $TenantId -From $principalKey -To $roleKeyById[$assignment.roleDefinitionId] -Relationship 'assignedRole' -State @{
                assignmentId = $assignment.id
                activation = 'active'
                assignment = if ($nodeById.ContainsKey($assignment.principalId) -and $nodeById[$assignment.principalId].Kind -eq 'group') {
                    'group'
                }
                else {
                    'direct'
                }
                directoryScopeId = $assignment.directoryScopeId
                appScopeId = $appScopeId
            } -EvidenceIds @($evidence.Key) -Source @{
                collector = 'directoryRoles'
            })
        )
    }

    foreach ($eligibility in $eligibilityResponse.Items) {
        $eligibilityStartDateTime = Get-AtlasResponseProperty -InputObject $eligibility -Name 'startDateTime'
        $eligibilityEndDateTime = Get-AtlasResponseProperty -InputObject $eligibility -Name 'endDateTime'
        $eligibilityMemberType = Get-AtlasResponseProperty -InputObject $eligibility -Name 'memberType'
        if (-not $roleKeyById.ContainsKey($eligibility.roleDefinitionId)) {
            $result.Status = 'partial'
            $result.Warnings.Add("Eligible role definition '$($eligibility.roleDefinitionId)' was not returned.")
            continue
        }

        if ($keyById.ContainsKey($eligibility.principalId)) {
            $principalKey = $keyById[$eligibility.principalId]
        }
        else {
            $principalNode = New-AtlasNode -TenantId $TenantId -Id $eligibility.principalId -Kind 'directoryObject' -DisplayName $eligibility.principalId -Status 'unresolved' -Source @{
                provider = 'microsoftGraph'
                apiVersion = 'v1.0'
                resourcePath = "/directoryObjects/$($eligibility.principalId)"
                collector = 'directoryRoleEligibility'
            }
            $result.Nodes.Add($principalNode)
            $principalKey = $principalNode.Key
        }

        $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'directoryRoleEligibility' -Endpoint $eligibilityEndpoint -SourceObjectId $eligibility.id -Fields @{
            principalId = $eligibility.principalId
            roleDefinitionId = $eligibility.roleDefinitionId
            directoryScopeId = $eligibility.directoryScopeId
            activation = 'eligible'
            startDateTime = $eligibilityStartDateTime
            endDateTime = $eligibilityEndDateTime
            memberType = $eligibilityMemberType
        }
        $result.Evidence.Add($evidence)
        $result.Edges.Add(
            (New-AtlasEdge -TenantId $TenantId -From $principalKey -To $roleKeyById[$eligibility.roleDefinitionId] -Relationship 'eligibleRole' -State @{
                assignmentId = $eligibility.id
                activation = 'eligible'
                assignment = if ($nodeById.ContainsKey($eligibility.principalId) -and $nodeById[$eligibility.principalId].Kind -eq 'group') {
                    'group'
                }
                else {
                    'direct'
                }
                directoryScopeId = $eligibility.directoryScopeId
                startDateTime = $eligibilityStartDateTime
                endDateTime = $eligibilityEndDateTime
                memberType = $eligibilityMemberType
            } -EvidenceIds @($evidence.Key) -Source @{
                collector = 'directoryRoleEligibility'
            })
        )
    }

    $result.Metrics = @{
        roleDefinitionCount = $definitionResponse.Items.Count
        roleAssignmentCount = $assignmentResponse.Items.Count
        roleEligibilityCount = $eligibilityResponse.Items.Count
        requestCount = $definitionResponse.Metrics.requestCount + $assignmentResponse.Metrics.requestCount + $eligibilityResponse.Metrics.requestCount
        retryCount = $definitionResponse.Metrics.retryCount + $assignmentResponse.Metrics.retryCount + $eligibilityResponse.Metrics.retryCount
    }
    return $result
}