Private/Get-ExchangeRBACData.ps1

function Get-RBACRoleGroups {
    <#
    .SYNOPSIS
        Get Exchange RBAC role groups
    .DESCRIPTION
        Retrieves role groups from Exchange with formatted data for display
    .EXAMPLE
        Get-RBACRoleGroups
    #>

    [CmdletBinding()]
    param()

    # Built-in Exchange Online role groups — exact names.
    $builtInRoleGroupNames = @(
        'Compliance Administrator', 'Compliance Management',
        'Communication Compliance', 'Communication Compliance Administrators',
        'Communication Compliance Investigators',
        'Delegated Setup', 'Discovery Management',
        'Help Desk', 'Hygiene Management',
        'Information Protection', 'Information Protection Admins',
        'Information Protection Analysts', 'Information Protection Investigators',
        'Information Protection Readers',
        'Insider Risk Management', 'Insider Risk Management Admins',
        'Insider Risk Management Investigators',
        'MailboxSearch', 'Organization Management',
        'Places Administrators',
        'Privacy Management', 'Privacy Management Administrators',
        'Privacy Management Investigators',
        'Public Folder Management', 'Recipient Management', 'Records Management',
        'Security Administrator', 'Security Operator', 'Security Reader',
        'Server Management', 'UM Management',
        'View-Only Organization Management', 'View-Only Recipient Management'
    )
    # Built-in role groups whose tenant-scoped suffix varies (e.g. "MailboxAdmins_1e071").
    $builtInRoleGroupPrefixes = @(
        'ISVMailboxUsers_', 'MailboxAdmins_', 'HelpdeskAdmins_',
        'TenantAdmins_', 'ExchangeServiceAdmins_',
        'GlobalReaders_', 'SecurityAdmins_', 'SecurityReaders_'
    )

    function Test-IsBuiltInRoleGroup {
        param($Group, [string[]]$Names, [string[]]$Prefixes)
        # Do NOT trust Get-RoleGroup's RoleGroupType for this check: in Exchange
        # Online both built-in and custom role groups commonly report
        # RoleGroupType='Standard', so matching on it classified every role group
        # as built-in and made the 'custom' chip return nothing.
        # The reliable signal is the curated name list + tenant-suffixed prefixes.
        $name = "$($Group.Name)".Trim()
        if ([string]::IsNullOrEmpty($name)) { return $false }
        foreach ($n in $Names) {
            if ([string]::Equals($n, $name, [System.StringComparison]::OrdinalIgnoreCase)) {
                return $true
            }
        }
        foreach ($p in $Prefixes) {
            if ($name.StartsWith($p, [System.StringComparison]::OrdinalIgnoreCase)) {
                return $true
            }
        }
        return $false
    }

    try {
        $roleGroups = Get-RoleGroup -ErrorAction Stop
        if (-not $roleGroups) { return @() }

        $displayData = foreach ($g in $roleGroups) {
            $isBuiltIn = Test-IsBuiltInRoleGroup -Group $g `
                          -Names $builtInRoleGroupNames -Prefixes $builtInRoleGroupPrefixes
            $origin = if ($isBuiltIn) { 'Built-in' } else { 'Custom' }

            [PSCustomObject]@{
                Name        = $g.Name
                Description = $g.Description
                Origin      = $origin
                MemberCount = if ($g.Members) { @($g.Members).Count } else { 0 }
                RoleCount   = if ($g.Roles)   { @($g.Roles).Count   } else { 0 }
                Members     = $g.Members
                Roles       = $g.Roles
            }
        }
        return $displayData
    }
    catch {
        throw "Error loading role groups: $_"
    }
}

function Get-RBACRoles {
    <#
    .SYNOPSIS
        Get Exchange RBAC roles
    .DESCRIPTION
        Retrieves management roles from Exchange with formatted data for display
    .EXAMPLE
        Get-RBACRoles
    #>

    [CmdletBinding()]
    param()
    
    try {
        $roles = Get-ManagementRole -ErrorAction Stop
        
        if ($roles -and $roles.Count -gt 0) {
            return $roles
        }
        else {
            return @()
        }
    }
    catch {
        throw "Failed to retrieve roles: $($_.Exception.Message)"
    }
}

function Get-RBACRoleAssignments {
    <#
    .SYNOPSIS
        Get Exchange RBAC role assignments
    .DESCRIPTION
        Retrieves role assignments from Exchange with formatted data for display
    .EXAMPLE
        Get-RBACRoleAssignments
    #>

    [CmdletBinding()]
    param()
    
    try {
        # Get all role assignments
        $rawRoleAssignments = Get-ManagementRoleAssignment

        # Remove duplicates by grouping on Name
        $roleAssignments = $rawRoleAssignments | Group-Object -Property Name | ForEach-Object { $_.Group[0] }

        if ($roleAssignments) {
            # Prepare data for display
            $displayData = $roleAssignments | ForEach-Object {
                # Format read scope with details if available
                $readScope = $_.RecipientReadScope
                if ($_.CustomRecipientReadScope) {
                    $readScope = "Custom: $($_.CustomRecipientReadScope)"
                    if ($_.ReadScopeDetails -and $_.ReadScopeDetails.RecipientFilter) {
                        $readScope += " (Filter: $($_.ReadScopeDetails.RecipientFilter))"
                    }
                }
                
                # Format write scope with details if available
                $writeScope = $_.RecipientWriteScope
                if ($_.CustomRecipientWriteScope) {
                    $writeScope = "Custom: $($_.CustomRecipientWriteScope)"
                    if ($_.WriteScopeDetails -and $_.WriteScopeDetails.RecipientFilter) {
                        $writeScope += " (Filter: $($_.WriteScopeDetails.RecipientFilter))"
                    }
                }
                
                [PSCustomObject]@{
                    Name                      = $_.Name
                    Role                      = $_.Role
                    RoleAssignee              = $_.RoleAssigneeName
                    RoleAssigneeType          = $_.RoleAssigneeType
                    Enabled                   = $_.Enabled
                    RecipientReadScope        = $readScope
                    RecipientWriteScope       = $writeScope
                    CustomRecipientReadScope  = $_.CustomRecipientReadScope
                    CustomRecipientWriteScope = $_.CustomRecipientWriteScope
                    ReadScopeDetails          = $_.ReadScopeDetails
                    WriteScopeDetails         = $_.WriteScopeDetails
                }
            }
            return $displayData
        }
        else {
            return @()
        }
    }
    catch {
        throw "Error loading role assignments: $_"
    }
}

function Get-RBACManagementScopes {
    <#
    .SYNOPSIS
        Get Exchange RBAC management scopes
    .DESCRIPTION
        Retrieves management scopes from Exchange with formatted data for display
    .EXAMPLE
        Get-RBACManagementScopes
    #>

    [CmdletBinding()]
    param()
    
    try {
        $scopes = Get-ManagementScope
        
        if ($scopes) {
            # Expose the full RecipientFilter as FilterSummary for the GUI; truncation
            # is the UI's job (wrap toggle, ellipsis with full-value tooltip).
            $displayData = $scopes | ForEach-Object {
                $filterSummary = if ($_.RecipientFilter) { [string]$_.RecipientFilter } else { '' }
                $_ | Add-Member -NotePropertyName 'FilterSummary' -NotePropertyValue $filterSummary -PassThru
            }
            return $displayData
        }
        else {
            return @()
        }
    }
    catch {
        throw "Error loading management scopes: $_"
    }
}