Public/Get-IOOrphanedRoleAssignments.ps1

function Get-IOOrphanedRoleAssignments {
    <#
    .SYNOPSIS
        Detects directory role assignments pointing at disabled or deleted users.
    .EXAMPLE
        Get-IOOrphanedRoleAssignments
    .EXAMPLE
        Get-IOOrphanedRoleAssignments -ToCsv "orphaned-roles.csv"
    #>

    [CmdletBinding()]
    param(
        [string]$ToCsv
    )

    $cmdName = $MyInvocation.MyCommand.Name
    Write-IOLog 'Scanning directory roles for orphaned assignments...' -Level Info -Component $cmdName

    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    $roles = Invoke-IOGraphRequest -Uri 'v1.0/directoryRoles?$select=id,displayName,roleTemplateId' -NoPagination

    $total   = ($roles | Measure-Object).Count
    $counter = 0

    try {
    foreach ($role in $roles) {
        $counter++
        if ($total -gt 0) {
            Write-Progress -Activity 'Checking role members' -Status "$($role.displayName) ($counter/$total)" -PercentComplete (($counter / $total) * 100)
        }

        try {
            $members = Invoke-IOGraphRequest -Uri "v1.0/directoryRoles/$($role.id)/members?`$select=id,displayName,userPrincipalName,accountEnabled,deletedDateTime,userType" -NoPagination -SkipConnectionCheck
        }
        catch {
            Write-IOLog "Could not read members of role '$($role.displayName)': $($_.Exception.Message)" -Level Verbose -Component $cmdName
            continue
        }

        foreach ($member in $members) {
            $isOrphaned = $false
            $reason     = ''

            if ($member.deletedDateTime) {
                $isOrphaned = $true
                $reason     = 'Deleted'
            }
            elseif ($member.PSObject.Properties['accountEnabled'] -and $member.accountEnabled -eq $false) {
                $isOrphaned = $true
                $reason     = 'Disabled'
            }

            if ($isOrphaned) {
                $results.Add([PSCustomObject]@{
                    RoleName          = $role.displayName
                    RoleId            = $role.id
                    MemberName        = $member.displayName
                    MemberUPN         = $member.userPrincipalName
                    MemberObjectId    = $member.id
                    MemberStatus      = $reason
                    MemberType        = if ($member.'@odata.type') { $member.'@odata.type'.Split('.')[-1] } else { 'Unknown' }
                })
            }
        }
    }
    }
    finally {
        Write-Progress -Activity 'Checking role members' -Completed
    }

    $sorted = $results | Sort-Object RoleName, MemberStatus
    Export-IOResult -Data $sorted -ToCsv $ToCsv -CommandName $cmdName
}