Private/RoleManagement/Get-PIMEligibleRoles.ps1
function Get-PIMEligibleRoles { <# .SYNOPSIS Retrieves eligible PIM roles for the current user. .DESCRIPTION Gets all eligible Privileged Identity Management (PIM) roles across Entra ID and Groups for the current user context. Azure resource roles are not included in this version. The function filters roles to only return those with 'Eligible' status and formats them with policy information and scope details for easy consumption. .EXAMPLE Get-PIMEligibleRoles Returns all eligible PIM roles for the current user across Entra ID and Groups. .OUTPUTS System.Object[] Array of role objects containing: - Type: Role type (Entra, Group) - DisplayName: Human-readable role name - PolicyInfo: Role policy configuration - ResourceName: Name of the resource the role applies to - RoleDefinitionId: Unique identifier for the role definition - Assignment: Assignment details - Scope: Formatted scope display (Directory, AU name, etc.) - Type-specific properties (DirectoryScopeId, GroupId, SubscriptionId) .NOTES Requires an active user context ($script:CurrentUser) to be available. Uses module-level flags $script:IncludeEntraRoles and $script:IncludeGroups to determine which role types to retrieve. #> [CmdletBinding()] param() Write-Verbose "Starting PIM eligible roles retrieval" # Validate current user context if (-not $script:CurrentUser -or -not $script:CurrentUser.Id) { Write-Verbose "No current user context available - returning empty array" return @() } Write-Verbose "Processing roles for user: $($script:CurrentUser.UserPrincipalName) ($($script:CurrentUser.Id))" # Prepare parameters for role retrieval $params = @{ UserId = $script:CurrentUser.Id IncludeEntraRoles = $script:IncludeEntraRoles IncludeGroups = $script:IncludeGroups IncludeAzureResources = $false # Explicitly disabled for this version } try { # Get all roles for the user $allRoles = Get-PIMRoles @params # Ensure we have an array to work with if ($null -eq $allRoles) { $allRoles = @() } elseif ($allRoles -isnot [array]) { $allRoles = @($allRoles) } Write-Verbose "Retrieved $($allRoles.Count) total roles" # Filter to eligible roles only $eligibleRoles = @($allRoles | Where-Object { $_.Status -eq 'Eligible' }) # Ensure we have an array after filtering if ($null -eq $eligibleRoles) { $eligibleRoles = @() } Write-Verbose "Found $($eligibleRoles.Count) eligible roles" # Process and format each eligible role $formattedRoles = foreach ($role in $eligibleRoles) { Write-Verbose "Processing role: $($role.Name) (Type: $($role.Type))" # Format scope display for better readability $scopeDisplay = Get-FormattedScopeDisplay -Role $role # Get policy information for the role $policyInfo = Get-PIMRolePolicy -Role $role # Create base formatted role object $formattedRole = [PSCustomObject]@{ Type = $role.Type DisplayName = $role.Name PolicyInfo = $policyInfo ResourceName = $role.ResourceName Assignment = $role.Assignment Scope = $scopeDisplay MemberType = $role.MemberType # This line ensures MemberType is passed through } # Add the appropriate ID property based on role type if ($role.Type -eq 'Group') { $formattedRole | Add-Member -NotePropertyName 'GroupId' -NotePropertyValue $role.ResourceId $formattedRole | Add-Member -NotePropertyName 'RoleDefinitionId' -NotePropertyValue $null } else { # Entra roles and others use RoleDefinitionId $formattedRole | Add-Member -NotePropertyName 'RoleDefinitionId' -NotePropertyValue $role.Id $formattedRole | Add-Member -NotePropertyName 'GroupId' -NotePropertyValue $null } # Add type-specific properties Add-TypeSpecificProperties -FormattedRole $formattedRole -SourceRole $role $formattedRole } # Ensure we return an array if ($null -eq $formattedRoles) { $formattedRoles = @() } elseif ($formattedRoles -isnot [array]) { $formattedRoles = @($formattedRoles) } Write-Verbose "Successfully processed $($formattedRoles.Count) eligible roles" return $formattedRoles } catch { Write-Error "Failed to retrieve eligible roles: $($_.Exception.Message)" Write-Verbose "Exception details: $($_.Exception.GetType().Name) - $($_.ScriptStackTrace)" return @() } } # Helper function to format scope display function Get-FormattedScopeDisplay { param($Role) $scopeDisplay = "Directory" if ($Role.DirectoryScopeId -and $Role.DirectoryScopeId -ne "/" -and $Role.DirectoryScopeId -ne "Directory") { if ($Role.DirectoryScopeId -match "^/administrativeUnits/(.+)$") { $auId = $Matches[1] try { $au = Get-MgDirectoryAdministrativeUnit -AdministrativeUnitId $auId -ErrorAction Stop $scopeDisplay = "AU: $($au.DisplayName)" } catch { Write-Verbose "Could not retrieve Administrative Unit name for ID: $auId" $scopeDisplay = "AU: $auId" } } else { $scopeDisplay = $Role.DirectoryScopeId } } return $scopeDisplay } # Helper function to add type-specific properties function Add-TypeSpecificProperties { param($FormattedRole, $SourceRole) switch ($SourceRole.Type) { 'Entra' { $FormattedRole | Add-Member -NotePropertyName 'DirectoryScopeId' -NotePropertyValue $SourceRole.DirectoryScopeId $FormattedRole | Add-Member -NotePropertyName 'SubscriptionId' -NotePropertyValue $null } 'Group' { $FormattedRole | Add-Member -NotePropertyName 'DirectoryScopeId' -NotePropertyValue $null $FormattedRole | Add-Member -NotePropertyName 'SubscriptionId' -NotePropertyValue $null } default { # Future Azure resource roles $FormattedRole | Add-Member -NotePropertyName 'DirectoryScopeId' -NotePropertyValue $null $FormattedRole | Add-Member -NotePropertyName 'SubscriptionId' -NotePropertyValue $null } } } |