Private/RoleManagement/Azure/Get-AzureMemberType.ps1

function Get-AzureMemberType {
    <#
    .SYNOPSIS
        Determines if an Azure role assignment is Direct or Inherited.
     
    .DESCRIPTION
        Analyzes the assignment scope relative to the current subscription and
        principal type to determine if the role assignment is direct or inherited.
        Matches Azure portal inheritance logic.
     
    .PARAMETER AssignmentScope
        The scope of the Azure role assignment.
     
    .PARAMETER CurrentSubscriptionId
        The ID of the current subscription being processed.
     
    .PARAMETER PrincipalType
        The type of principal (User, Group, ServicePrincipal).
     
    .PARAMETER IsEligible
        Whether this is an eligible assignment (affects inheritance logic).
     
    .PARAMETER ObjectId
        The object ID of the principal for additional validation.
     
    .OUTPUTS
        String indicating "Direct", "Inherited", or "Group"
    #>

    param(
        [Parameter(Mandatory)]
        [string]$AssignmentScope,
        
        [Parameter(Mandatory)]
        [string]$CurrentSubscriptionId,
        
        [Parameter()]
        [string]$PrincipalType = "User",
        
        [Parameter()]
        [bool]$IsEligible = $false,
        
        [Parameter()]
        [string]$ObjectId
    )
    
    Write-Verbose "Analyzing member type for scope: $AssignmentScope (PrincipalType: $PrincipalType, CurrentSub: $CurrentSubscriptionId)"
    
    # Group-based assignments are always "Group" type
    if ($PrincipalType -eq 'Group') {
        Write-Verbose "Assignment is Group-based -> Group"
        return "Group"
    }
    
    # Management group assignments are ALWAYS inherited
    if ($AssignmentScope -match "^/providers/Microsoft\.Management/managementGroups/") {
        Write-Verbose "Assignment at management group scope -> Inherited"
        return "Inherited"
    }
    
    # Tenant root assignments (scope = "/") are ALWAYS inherited
    if ($AssignmentScope -eq "/") {
        Write-Verbose "Assignment at tenant root scope -> Inherited"
        return "Inherited"
    }
    
    # Cross-subscription assignments are inherited
    if ($AssignmentScope -match "^/subscriptions/([^/]+)") {
        $assignmentSubscriptionId = $matches[1]
        if ($assignmentSubscriptionId -ne $CurrentSubscriptionId) {
            Write-Verbose "Assignment from different subscription ($assignmentSubscriptionId) -> Inherited"
            return "Inherited"
        }
    }
    
    # For assignments within the current subscription
    if ($AssignmentScope -match "^/subscriptions/$CurrentSubscriptionId") {
        # Exact subscription level assignments
        if ($AssignmentScope -eq "/subscriptions/$CurrentSubscriptionId") {
            Write-Verbose "Assignment at current subscription level -> Direct"
            return "Direct"
        }
        
        # Resource group level assignments are direct
        if ($AssignmentScope -match "^/subscriptions/$CurrentSubscriptionId/resourceGroups/[^/]+$") {
            Write-Verbose "Assignment at resource group level -> Direct"
            return "Direct"
        }
        
        # Individual resource assignments are direct
        if ($AssignmentScope -match "^/subscriptions/$CurrentSubscriptionId/resourceGroups/.+") {
            Write-Verbose "Assignment at resource level -> Direct"
            return "Direct"
        }
    }
    
    # Special case: Check if the scope suggests inheritance from a higher level
    # This handles cases where the assignment might be coming from a scope above the current context
    
    # If the scope doesn't contain the current subscription, it's likely inherited
    if ($AssignmentScope -notmatch $CurrentSubscriptionId) {
        # Check if it's a well-known inherited scope pattern
        if ($AssignmentScope -match "^/$" -or 
            $AssignmentScope -match "^/providers/" -or
            $AssignmentScope -match "managementGroups") {
            Write-Verbose "Assignment from higher scope pattern -> Inherited"
            return "Inherited"
        }
    }
    
    # For any assignment that doesn't fit the above patterns but is clearly from a broader scope
    # Check scope hierarchy depth - if it's "shorter" than our subscription scope, it's likely inherited
    $assignmentParts = ($AssignmentScope -split '/').Where({ $_ -ne '' })
    $subscriptionParts = ("/subscriptions/$CurrentSubscriptionId" -split '/').Where({ $_ -ne '' })
    
    if ($assignmentParts.Count -lt $subscriptionParts.Count) {
        Write-Verbose "Assignment scope shorter than subscription scope -> Inherited"
        return "Inherited"
    }
    
    # Default case - if we can't determine inheritance, assume direct
    Write-Verbose "Could not determine inheritance pattern -> Direct (default)"
    return "Direct"
}