Public/Discovery/Get-RoleAssignments.ps1

function Get-RoleAssignments {
    [cmdletbinding()]
    param (
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Alias('current-user')]
        [switch]$CurrentUser,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [ValidateSet('User', 'Group', 'ServicePrincipal', 'Other')]
        [Alias('principal-type')]
        [string]$PrincipalType,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Alias('is-custom')]
        [switch]$IsCustom,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Alias('object-id')]
        [string]$ObjectId,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('^[0-9a-fA-F-]{36}$', ErrorMessage = "It does not match expected pattern '{1}'")]
        [Alias('subscription-id')]
        [string]$SubscriptionId,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Alias('skip-custom')]
        [switch]$SkipCustom,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Alias('throttle-limit')]
        [int]$ThrottleLimit = 10
    )

    begin {
        Write-Verbose "Starting function $($MyInvocation.MyCommand.Name)"
        $MyInvocation.MyCommand.Name | Invoke-BlackCat

        $roleAssignmentsList = [System.Collections.Concurrent.ConcurrentBag[PSCustomObject]]::new()
        $userAgents = $sessionVariables.userAgents.agents
        $subscriptions = @()
    }

    process {
        try {
            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "Retrieving all subscriptions for the current user context" -Severity 'Information'
            $baseUri = 'https://management.azure.com'
            $subscriptionsUri = "$($baseUri)/subscriptions?api-version=2020-01-01"
            $requestParam = @{
                Headers = $script:authHeader
                Uri     = $subscriptionsUri
                Method  = 'GET'
            }

            if ($SubscriptionId) {
                $requestParam.Uri += '&$filter={0}' -f "subscriptionId eq '$SubscriptionId'"
            }

            $subscriptions = (Invoke-RestMethod @requestParam).value.subscriptionId

        }
        catch {
            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message $($_.Exception.Message) -Severity 'Error'
        }

        try {
            if ($CurrentUser) {
                Write-Verbose "Retrieving current user's object Id"
                $ObjectId = (Get-CurrentUser).Id
            }

            if ($ObjectId) {
                Write-Verbose "Retrieving groups for user: $ObjectId"
                $Groups = @(Invoke-MsGraph -relativeUrl "users/$ObjectId/memberOf").id
            }
            else {
                $Groups = @()
            }

            Write-Verbose  "Retrieving role assignments for $($subscriptions.Count) subscriptions"
            $subscriptions | ForEach-Object -Parallel {
                try {
                    $baseUri             = $using:baseUri
                    $authHeader          = $using:script:authHeader
                    $userAgents          = $using:userAgents
                    $roleAssignmentsList = $using:roleAssignmentsList
                    $ObjectId            = $using:ObjectId
                    $Groups              = $using:Groups
                    $azureRoles          = $using:script:SessionVariables.AzureRoles
                    $PrincipalType       = $using:PrincipalType
                    $IsCustom            = $using:IsCustom
                    $SkipCustom          = $using:SkipCustom
                    $subscriptionId      = $_

                    Write-Verbose "Retrieving role assignments for subscription: $subscriptionId"
                    $roleAssignmentsUri = "$($baseUri)/subscriptions/$subscriptionId/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01"

                    $principalIds = @()
                    if ($ObjectId) {
                        $principalIds += $ObjectId
                    }
                    if ($Groups.length -gt 0) {
                        $principalIds += $Groups
                    }

                    $roleAssignmentsResponse = @()
                    $roleAssignmentsRequestParam = @{
                        Headers   = $authHeader
                        Method    = 'GET'
                        Uri       = $roleAssignmentsUri
                        UserAgent = $($userAgents.value | Get-Random)
                    }

                    if ($principalIds) {
                        foreach ($principalId in $principalIds) {
                            $roleAssignmentsRequestParam.Uri = "$roleAssignmentsUri&`$filter=principalId eq '$principalId'"
                            $roleAssignmentsResponse += (Invoke-RestMethod @roleAssignmentsRequestParam).value
                        }
                    } else {
                        $roleAssignmentsResponse += @(Invoke-RestMethod @roleAssignmentsRequestParam).value
                    }

                    if ($PrincipalType) {
                        $roleAssignmentsResponse = $roleAssignmentsResponse | Where-Object { $_.properties.principalType -eq $PrincipalType }
                    }

                    foreach ($roleAssignment in $roleAssignmentsResponse) {
                        if ($roleAssignment.properties.principalType) {
                            $roleAssignmentObject = [PSCustomObject]@{
                                PrincipalType = $roleAssignment.properties.principalType
                                PrincipalId   = $roleAssignment.properties.principalId
                                Scope         = $roleAssignment.properties.scope
                                RoleId        = $roleAssignment.properties.roleDefinitionId -split '/' | Select-Object -Last 1
                                IsCustom      = $false
                            }

                            $roleId = ($roleAssignment.properties.roleDefinitionId -split '/')[-1]
                            $roleName = ($azureRoles | Where-Object { $_.id -match $roleId } ).Name

                            if (-not($roleName)) {
                                $roleDefinitionsUri = "$($baseUri)/subscriptions/$subscriptionId/providers/Microsoft.Authorization/roleDefinitions/$($roleId)?`$filter=type eq 'CustomRole'&api-version=2022-05-01-preview"
                                $roleDefinitionsRequestParam = @{
                                    Headers = $authHeader
                                    Uri     = $roleDefinitionsUri
                                    Method  = 'GET'
                                    UserAgent = $($userAgents.value | Get-Random)
                                }

                                if (-not $SkipCustom) {
                                    Write-Verbose "Retrieving custom role definition for subscription: $subscriptionId"
                                    $roleName = (Invoke-RestMethod @roleDefinitionsRequestParam).properties.roleName
                                }

                                $roleAssignmentObject.IsCustom = $true
                            }

                            if ($roleName) {
                                $memberObject = @{
                                    MemberType = 'NoteProperty'
                                    Name       = 'RoleName'
                                    Value      = $roleName
                                }
                                $roleAssignmentObject | Add-Member @memberObject

                                if (-not $IsCustom -or $roleAssignmentObject.IsCustom) {
                                    $roleAssignmentsList.Add($roleAssignmentObject)
                                }
                            }
                        }
                    }
                }
                catch {
                    Write-Information "$($MyInvocation.MyCommand.Name): Error processing subscription '$_'" -InformationAction Continue
                }
            } -ThrottleLimit $ThrottleLimit
        }
        catch {
            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message $($_.Exception.Message) -Severity 'Error'
        }

        if ($roleAssignmentsList.Count -eq 0) {
            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) "No role assignments found for the specified criteria." -severity 'Information'
        }

        Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "Completed" -Severity 'Information'
        return $roleAssignmentsList
    }

    <#
    .SYNOPSIS
        Retrieves role assignments across Azure subscriptions for the authenticated context.

    .DESCRIPTION
        Get-RoleAssignments queries Azure RBAC (Role Based Access Control) assignments across all accessible
        subscriptions or a specified subscription. It supports filtering by principal type, object ID, and
        can isolate assignments for the current user.

        The function utilizes parallel processing to optimize performance when querying multiple subscriptions,
        with configurable throttling to respect API limits.

    .PARAMETER CurrentUser
        Switch parameter to filter results to only show role assignments for the currently authenticated user.

    .PARAMETER PrincipalType
        Filters results by principal type. Valid options:
        - User
        - Group
        - ServicePrincipal
        - Other

    .PARAMETER ObjectId
        Filters results by the specific Azure AD Object ID (GUID).

    .PARAMETER SubscriptionId
        Limits query to a single subscription ID. If omitted, queries all accessible subscriptions.

    .PARAMETER ThrottleLimit
        Maximum number of concurrent operations. Default is 10 to respect RBAC API rate limits.
        Performance notes:
        - 10 concurrent operations: ~2 minutes for 175 subscriptions/23K assignments
        - 1000 concurrent operations: ~8 minutes for same scope

    .OUTPUTS
        Array of custom objects containing:
        - PrincipalType: The Azure AD principal type
        - PrincipalId: The Azure AD object ID of the principal
        - RoleName: The display name of the RBAC role
        - Scope: The resource scope of the assignment

    .EXAMPLE
        Get-RoleAssignments -CurrentUser
        Returns all role assignments for the authenticated user across all accessible subscriptions.

    .EXAMPLE
        Get-RoleAssignments -PrincipalType Group
        Lists all role assignments granted to Azure AD groups across all accessible subscriptions.

    .EXAMPLE
        Get-RoleAssignments -PrincipalType ServicePrincipal -ObjectId '00000000-0000-0000-0000-000000000000'
        Retrieves role assignments for a specific service principal identified by its Object ID.

    .EXAMPLE
        Get-RoleAssignments -SubscriptionId '00000000-0000-0000-0000-000000000000' -ThrottleLimit 20
        Gets all role assignments in the specified subscription with increased concurrent operations.

    .NOTES
        Requires appropriate Azure RBAC permissions to read role assignments at the queried scope.
        Consider API rate limits when adjusting ThrottleLimit parameter.
    #>

}