Private/RoleManagement/Get-GroupRoles.ps1

function Get-GroupRoles {
    <#
    .SYNOPSIS
        Retrieves PIM-enabled group memberships for a user.
     
    .DESCRIPTION
        Gets both active and eligible PIM group memberships from Microsoft Graph API.
        Returns standardized role objects that include group details, membership status,
        and access type (member or owner).
     
    .PARAMETER UserId
        The Azure AD user ID (GUID) to retrieve group memberships for.
     
    .EXAMPLE
        Get-GroupRoles -UserId "12345678-1234-1234-1234-123456789012"
        Retrieves all PIM group memberships for the specified user.
     
    .EXAMPLE
        Get-GroupRoles -UserId $env:UserPrincipalName -Verbose
        Retrieves group memberships with detailed verbose output.
     
    .OUTPUTS
        PSCustomObject[]
        Returns an array of objects containing group membership details including:
        - Id: Group ID
        - Name: Group display name
        - Type: Always 'Group'
        - Status: 'Eligible' or 'Active'
        - MemberType: 'member' or 'owner'
        - StartDateTime/EndDateTime: Assignment validity period
     
    .NOTES
        Requires Microsoft Graph PowerShell SDK with appropriate permissions:
        - PrivilegedAccess.Read.AzureADGroup
        - Group.Read.All (for group details)
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string]$UserId
    )
    
    begin {
        $roles = [System.Collections.ArrayList]::new()
    }
    
    process {
        try {
            Write-Verbose "Retrieving PIM group memberships for user: $UserId"
            
            # Get eligible group memberships
            Write-Verbose "Querying eligible group memberships..."
            $eligibleGroups = $null
            try {
                $eligibleParams = @{
                    Filter = "principalId eq '$UserId'"
                    ExpandProperty = 'group'
                    ErrorAction = 'Stop'
                }
                $eligibleGroups = Get-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleInstance @eligibleParams
            }
            catch {
                Write-Verbose "No eligible groups found or access denied: $($_.Exception.Message)"
                $eligibleGroups = @()
            }
            
            # Normalize to array
            $eligibleGroups = @($eligibleGroups)
            Write-Verbose "Found $($eligibleGroups.Count) eligible group membership(s)"
            
            # Process eligible memberships
            foreach ($membership in $eligibleGroups) {
                if ($membership.Group) {
                    Write-Verbose "Processing eligible group: $($membership.Group.DisplayName) (Access: $($membership.AccessId))"
                    
                    $null = $roles.Add([PSCustomObject]@{
                        Id = $membership.GroupId
                        Name = $membership.Group.DisplayName
                        Type = 'Group'
                        Status = 'Eligible'
                        Source = 'PIMGroup'
                        ResourceId = $membership.GroupId
                        ResourceName = $membership.Group.DisplayName
                        StartDateTime = $membership.StartDateTime
                        EndDateTime = $membership.EndDateTime
                        MemberType = $membership.AccessId
                        DirectoryScopeId = $null
                        PrincipalId = $membership.PrincipalId
                        Assignment = $membership
                    })
                }
            }
            
            # Get active group memberships
            Write-Verbose "Querying active group memberships..."
            $activeGroups = $null
            try {
                $activeParams = @{
                    Filter = "principalId eq '$UserId'"
                    ExpandProperty = 'group'
                    ErrorAction = 'Stop'
                }
                $activeGroups = Get-MgIdentityGovernancePrivilegedAccessGroupAssignmentScheduleInstance @activeParams
            }
            catch {
                Write-Verbose "No active groups found or access denied: $($_.Exception.Message)"
                $activeGroups = @()
            }
            
            # Normalize to array
            $activeGroups = @($activeGroups)
            Write-Verbose "Found $($activeGroups.Count) active group membership(s)"
            
            # Process active memberships
            foreach ($membership in $activeGroups) {
                if ($membership.Group) {
                    Write-Verbose "Processing active group: $($membership.Group.DisplayName) (Access: $($membership.AccessId))"
                    
                    $null = $roles.Add([PSCustomObject]@{
                        Id = $membership.GroupId
                        Name = $membership.Group.DisplayName
                        Type = 'Group'
                        Status = 'Active'
                        Source = 'PIMGroup'
                        ResourceId = $membership.GroupId
                        ResourceName = $membership.Group.DisplayName
                        StartDateTime = $membership.StartDateTime
                        EndDateTime = $membership.EndDateTime
                        MemberType = $membership.AccessId
                        DirectoryScopeId = $null
                        PrincipalId = $membership.PrincipalId
                        Assignment = $membership
                    })
                }
            }
            
            Write-Verbose "Retrieved $($roles.Count) total PIM group membership(s)"
        }
        catch {
            Write-Warning "Failed to retrieve group memberships: $($_.Exception.Message)"
            Write-Verbose "Full error details: $($_.Exception.ToString())"
        }
    }
    
    end {
        # Always return an array
        return @($roles)
    }
}