Public/Test-CallerRBACAssignment.ps1

function Test-CallerRBACAssignment {
    <#
    .SYNOPSIS
        Tests whether a caller already holds the required role(s) at a scope.
 
    .DESCRIPTION
        Checks the caller's effective Azure role assignments at (or above) the
        supplied scope against a required role set. Returns one result object per
        required role with a State boolean indicating whether the caller has it.
 
        Role assignment lookups use Get-AzRoleAssignment. The Az.Resources module
        must be available and a context established (Connect-AzAccount) for live
        checks. When -RoleAssignment is supplied the caller's assignments are
        taken from that array instead, which makes the function fully testable
        without a live connection.
 
    .PARAMETER CallerId
        The caller identity to evaluate: a UPN, object id, or sign-in name.
 
    .PARAMETER RequiredRole
        One or more role names the caller is expected to hold.
 
    .PARAMETER Scope
        The Azure scope to evaluate the assignments at.
 
    .PARAMETER RoleAssignment
        Optional pre-fetched role assignment objects (each with RoleDefinitionName
        and Scope). When supplied, no live Az call is made.
 
    .EXAMPLE
        Test-CallerRBACAssignment -CallerId 'patrick.gallucci@microsoft.com' `
            -RequiredRole 'Reader' -Scope '/subscriptions/00000000-0000-0000-0000-000000000000'
 
    .OUTPUTS
        PSCustomObject per role: CallerId, Role, Scope, State.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$CallerId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string[]]$RequiredRole,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Scope,

        [Parameter()]
        [object[]]$RoleAssignment
    )

    $scope = $Scope.TrimEnd('/')

    if (-not $PSBoundParameters.ContainsKey('RoleAssignment')) {
        if (-not (Get-Command -Name 'Get-AzRoleAssignment' -ErrorAction SilentlyContinue)) {
            throw "Get-AzRoleAssignment is not available. Install/Import Az.Resources or pass -RoleAssignment for offline evaluation."
        }
        try {
            $RoleAssignment = @(Get-AzRoleAssignment -SignInName $CallerId -ErrorAction Stop)
        }
        catch {
            Write-Warning "Falling back to object-id lookup: $($_.Exception.Message)"
            $RoleAssignment = @(Get-AzRoleAssignment -ObjectId $CallerId -ErrorAction Stop)
        }
    }

    foreach ($role in $RequiredRole) {
        # The caller holds the role if assigned it at this scope or any ancestor.
        $match = $RoleAssignment | Where-Object {
            $_.RoleDefinitionName -eq $role -and
            $scope.StartsWith(($_.Scope.TrimEnd('/')), [System.StringComparison]::OrdinalIgnoreCase)
        } | Select-Object -First 1

        [pscustomobject]@{
            PSTypeName = 'PSAutoRBAC.AssignmentState'
            CallerId   = $CallerId
            Role       = $role
            Scope      = $scope
            State      = [bool]$match
        }
    }
}