Private/Identity/Resolve-GroupMembership.ps1

function Resolve-GroupMembership {
    <#
    .SYNOPSIS
        Resolves group memberships for users and converts group IDs to readable names.

    .DESCRIPTION
        This function can perform two operations:
        1. Retrieve all groups a user belongs to
        2. Check if a user is a member of specific groups (inclusion/exclusion lists)
        It resolves GUIDs to readable group names for reporting.
        Uses optimized group membership retrieval with caching for better performance.

    .PARAMETER UserId
        The user ID (GUID) to check group membership for

    .PARAMETER ServicePrincipalId
        The service principal ID (GUID) to check group membership for

    .PARAMETER GroupIds
        Optional. Specific group IDs to check membership against (for CA policy inclusion/exclusion lists)

    .PARAMETER IncludeNestedGroups
        Whether to include transitive group memberships (nested groups)

    .PARAMETER ForceRefresh
        Forces a refresh of the group membership cache

    .EXAMPLE
        # Get all groups for a user
        Resolve-GroupMembership -UserId "846eca8a-95ce-4d54-a45c-37b5fea0e3a8"

    .EXAMPLE
        # Check if user is a member of specific groups
        Resolve-GroupMembership -UserId "846eca8a-95ce-4d54-a45c-37b5fea0e3a8" -GroupIds @("groupId1", "groupId2")

    .EXAMPLE
        # Check service principal group membership
        Resolve-GroupMembership -ServicePrincipalId "846eca8a-95ce-4d54-a45c-37b5fea0e3a8" -GroupIds @("groupId1")
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$UserId,

        [Parameter(Mandatory = $false)]
        [string]$ServicePrincipalId,

        [Parameter(Mandatory = $false)]
        [string[]]$GroupIds,

        [Parameter(Mandatory = $false)]
        [switch]$IncludeNestedGroups = $true,

        [Parameter(Mandatory = $false)]
        [switch]$ForceRefresh
    )

    try {
        # Ensure either UserId or ServicePrincipalId is provided
        if (-not $UserId -and -not $ServicePrincipalId) {
            throw "Either UserId or ServicePrincipalId must be specified"
        }

        # Initialize results
        $result = [PSCustomObject]@{
            UserId                 = $UserId
            ServicePrincipalId     = $ServicePrincipalId
            Groups                 = @()
            MemberOfSpecificGroups = @{}
            Success                = $false
            Error                  = $null
        }

        # Log diagnostic information
        $entityType = if ($UserId) { "user $UserId" } else { "service principal $ServicePrincipalId" }
        Write-DiagnosticOutput -Source "Resolve-GroupMembership" -Message "Retrieving group memberships for $entityType" -Level "Info"

        # Get group memberships using the optimized function
        $membershipParams = @{
            IncludeNestedGroups = $IncludeNestedGroups
            ForceRefresh        = $ForceRefresh
        }

        if ($UserId) {
            $membershipParams.UserId = $UserId
        }
        else {
            $membershipParams.ServicePrincipalId = $ServicePrincipalId
        }

        # If specific groups are specified, include them in the parameters
        if ($GroupIds -and $GroupIds.Count -gt 0) {
            $membershipParams.GroupIds = $GroupIds
        }

        # Use the optimized function to get memberships
        $groupMemberships = Get-OptimizedGroupMembership @membershipParams

        # Process the results
        if (-not $GroupIds -or $GroupIds.Count -eq 0) {
            # Return all groups the entity belongs to
            foreach ($group in $groupMemberships) {
                $result.Groups += [PSCustomObject]@{
                    Id             = $group.id
                    DisplayName    = $group.displayName
                    Description    = $group.description
                    MembershipType = "Transitive" # Using optimized function always gets transitive memberships
                }
            }
        }
        else {
            # Process specific groups for membership checking
            foreach ($groupId in $GroupIds) {
                $groupMembership = $groupMemberships | Where-Object { $_.id -eq $groupId }
                $isMember = $null -ne $groupMembership

                # Get the group details
                $groupDisplayName = if ($isMember) {
                    $groupMembership.displayName
                }
                else {
                    # Try to get the group name anyway for reporting
                    try {
                        $errorOutput = $null
                        $groupDetails = Get-MgGroup -GroupId $groupId -ErrorAction SilentlyContinue -ErrorVariable errorOutput
                        if ($errorOutput) {
                            Write-DiagnosticOutput -Source "Resolve-GroupMembership" -Message "Group ID $groupId not found or inaccessible (this is normal for non-existent groups)" -Level "Debug"
                            "Unknown Group"
                        }
                        else {
                            $groupDetails.DisplayName
                        }
                    }
                    catch {
                        $errorMessage = $_.Exception.Message
                        Write-DiagnosticOutput -Source "Resolve-GroupMembership" -Message "Error retrieving group details: $errorMessage" -Level "Debug"
                        "Unknown Group"
                    }
                }

                $result.MemberOfSpecificGroups[$groupId] = [PSCustomObject]@{
                    GroupId        = $groupId
                    DisplayName    = $groupDisplayName
                    IsMember       = $isMember
                    MembershipType = if ($isMember) { "Transitive" } else { "None" }
                }
            }
        }

        $result.Success = $true
        return $result
    }
    catch {
        $errorMsg = $_.Exception.Message
        Write-DiagnosticOutput -Source "Resolve-GroupMembership" -Message "Error resolving group membership: $errorMsg" -Level "Error"
        $result.Success = $false
        $result.Error = $errorMsg
        return $result
    }
}