In a previous post I wrote a script to be able to get Entra ID Role assignments using the older Azure AD PowerShell module. However, with the addition of Graph API and seeing how that’s the way of the future, I wanted to share my updated script to use Graph API instead. This will still require the Graph API PowerShell module since it uses some PowerShell cmdlets instead of the native REST calls, but it’s great to use and outputs the information we require.
Table Of Contents
Requirements
In this article I am going to be sharing a script to get the Entra ID PIM Role eligibility and active assignments but there are a few things we will need in order to run the script successfully. Let us list out what’s needed now.
- Graph PowerShell SDK v1.0 and beta module
- Entra ID P2 License
- Graph API Scopes:
- Directory.Read.All
- RoleEligibilitySchedule.Read.Directory
- RoleAssignmentSchedule.Read.Directory
- RoleManagement.Read.Directory
–OR–
Get Entra ID PIM Role Assignment Using Graph API
As mentioned above, we will need at least 1 Entra ID P2 license since that is what allows us to use PIM in our tenant. We should also confirm we have the Graph PowerShell SDK v1.0 and beta modules.
Finally, I like to use PowerShell 7+ since that is better optimized for PowerShell as opposed to the default Windows PowerShell that comes pre-installed with Windows. This is not a requirement but more of a personal preference.
PowerShell Script
Now let’s get to the reason why you checked this article. Below is the PowerShell script to get PIM Role assignment using Graph API.
Function Get-MgPimRoleAssignment { <# .SYNOPSIS This will check if a user is added to PIM or standing access. .LINK https://thesysadminchannel.com/get-entra-id-pim-role-assignment-using-graph-api - .NOTES Name: Get-MgPimRoleAssignment Author: Paul Contreras Version: 2.4 DateCreated: 2023-Jun-15 #> [CmdletBinding()] param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'User', Position = 0 )] [Alias('UserPrincipalName')] [string[]] $UserId, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Role', Position = 1 )] [Alias('DisplayName')] [ValidateSet( 'Application administrator', 'Application developer', 'Attack payload author', 'Attack simulation administrator', 'Attribute assignment administrator', 'Attribute assignment reader', 'Attribute definition administrator', 'Attribute definition reader', 'Authentication administrator', 'Authentication policy administrator', 'Azure AD joined device local administrator', 'Azure DevOps administrator', 'Azure Information Protection administrator', 'B2C IEF Keyset administrator', 'B2C IEF Policy administrator', 'Billing administrator', 'Cloud App Security Administrator', 'Cloud application administrator', 'Cloud device administrator', 'Compliance administrator', 'Compliance data administrator', 'Conditional Access administrator', 'Customer LockBox access approver', 'Desktop Analytics administrator', 'Directory readers', 'Directory writers', 'Domain name administrator', 'Dynamics 365 administrator', 'Edge administrator', 'Exchange administrator', 'Exchange recipient administrator', 'External ID user flow administrator', 'External ID user flow attribute administrator', 'External Identity Provider administrator', 'Global administrator', 'Global reader', 'Groups administrator', 'Guest inviter', 'Helpdesk administrator', 'Hybrid identity administrator', 'Identity Governance Administrator', 'Insights administrator', 'Insights Analyst', 'Insights business leader', 'Intune administrator', 'Kaizala administrator', 'Knowledge administrator', 'Knowledge manager', 'License administrator', 'Lifecycle Workflows Administrator', 'Message center privacy reader', 'Message center reader', 'Network administrator', 'Office apps administrator', 'Password administrator', 'Permissions Management Administrator', 'Power BI administrator', 'Power platform administrator', 'Printer administrator', 'Printer technician', 'Privileged authentication administrator', 'Privileged role administrator', 'Reports reader', 'Search administrator', 'Search editor', 'Security administrator', 'Security operator', 'Security reader', 'Service support administrator', 'SharePoint administrator', 'Skype for Business administrator', 'Teams administrator', 'Teams communications administrator', 'Teams Communications Support Engineer', 'Teams Communications Support Specialist', 'Teams devices administrator', 'Tenant Creator', 'Usage summary reports reader', 'User administrator', 'Virtual Visits Administrator', 'Windows 365 Administrator', 'Windows update deployment administrator', 'Yammer Administrator' )] [string] $RoleName, [Parameter( Mandatory = $false )] [ValidateSet( 'Eligibile', 'Active' )] [string] $PimAssignment, [Parameter( Mandatory = $false )] [string] $TenantId, [Parameter( Mandatory = $false )] [switch] $HideActivatedRoles ) BEGIN { $ConnectionGraph = Get-MgContext $ConnectionGraph.Scopes = $ConnectionGraph.Scopes -replace "write","" | select -Unique 'RoleEligibilitySchedule.Read.Directory', 'RoleAssignmentSchedule.Read.Directory', 'RoleManagement.Read.Directory' | ForEach-Object { if ($ConnectionGraph.Scopes -notcontains $_) { Connect-Graph -Scopes RoleEligibilitySchedule.Read.Directory, RoleAssignmentSchedule.Read.Directory, RoleManagement.Read.Directory -ErrorAction Stop continue } } if (-not ($PSBoundParameters.ContainsKey('TenantId'))) { $TenantId = $ConnectionGraph.TenantId } } PROCESS { $RoleDefinitions = Invoke-GraphRequest -Uri 'https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions' | select -ExpandProperty value $RoleHash = @{} $RoleDefinitions | select id, displayname | ForEach-Object {$RoleHash.Add($_.DisplayName, $_.Id) | Out-Null} $RoleDefinitions | select id, displayname | ForEach-Object {$RoleHash.Add($_.Id, $_.DisplayName) | Out-Null} if ($PSBoundParameters.ContainsKey('UserId')) { foreach ($User in $UserId) { try { [System.Collections.Generic.List[Object]]$RoleMemberList = @() $PropertyList = 'DisplayName', 'UserPrincipalName', 'Id', 'AccountEnabled' $AzUser = Get-MgUser -UserId $User -Property $PropertyList | select $PropertyList if ($PSBoundParameters.ContainsKey('PimAssignment')) { #if active or eligible is selected, no need to get other option if ($PSBoundParameters.ContainsValue('Active')) { $AssignmentList = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter "PrincipalId eq '$($AzUser.id)'" -ExpandProperty Principal,DirectoryScope -All $AssignmentList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value "Active" -Force -PassThru | Out-Null $AssignmentList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties."@odata.type".split('.')[2] } -Force -PassThru | Out-Null $AssignmentList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null} } if ($PSBoundParameters.ContainsValue('Eligibile')) { $EligibleList = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter "PrincipalId eq '$($AzUser.id)'" -ExpandProperty Principal,DirectoryScope -All $EligibleList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value "Eligibile" -Force -PassThru | Out-Null $EligibleList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties."@odata.type".split('.')[2] } -Force -PassThru | Out-Null $EligibleList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null} } } else { $AssignmentList = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter "PrincipalId eq '$($AzUser.id)'" -ExpandProperty Principal,DirectoryScope -All $AssignmentList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value "Active" -Force -PassThru | Out-Null $AssignmentList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties."@odata.type".split('.')[2] } -Force -PassThru | Out-Null $EligibleList = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter "PrincipalId eq '$($AzUser.id)'" -ExpandProperty Principal,DirectoryScope -All $EligibleList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value "Eligibile" -Force -PassThru | Out-Null $EligibleList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties."@odata.type".split('.')[2] } -Force -PassThru | Out-Null $AssignmentList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null} $EligibleList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null} } if ($RoleMemberList) { $Output = foreach ($RoleMember in $RoleMemberList) { if ($RoleMember.DirectoryScopeId -eq '/') { $DirectoryScope = 'Global' } elseif ($RoleMember.DirectoryScopeId -match 'administrativeUnits') { $DirectoryScope = $RoleMember.DirectoryScope.AdditionalProperties.displayName } else { $DirectoryScope = 'Unknown' } if ($RoleMember.ScheduleInfo.Expiration.Type -eq 'noExpiration') { $DurationInMonths = 'Permanent' $EndDate = 'Permanent' } else { $Days = ($RoleMember.ScheduleInfo.Expiration.EndDateTime) - ($RoleMember.ScheduleInfo.StartDateTime) | select -ExpandProperty TotalDays $DurationInMonths = $Days / 30.4167 -as [int] $EndDate = (Get-Date $RoleMember.ScheduleInfo.Expiration.EndDateTime).ToLocalTime() } if ($RoleMember.AssignmentScope -eq 'Active' -and $RoleMember.AssignmentType -eq 'Activated') { $AssignmentScope = 'PimActivated' } else { $AssignmentScope = $RoleMember.AssignmentScope } if ($RoleMember.ScheduleInfo.StartDateTime -and $RoleMember.CreatedDateTime) { $StartDateTime = (Get-Date $RoleMember.ScheduleInfo.StartDateTime).ToLocalTime() } else { $StartDateTime = (Get-Date 1/1/1999 -Hour 0 -Minute 0 -Millisecond 0) } [PSCustomObject]@{ UserPrincipalName = $AzUser.UserPrincipalName AzureADRole = $RoleHash[$RoleMember.RoleDefinitionId] PimAssignment = $AssignmentScope EndDateTime = $EndDate AccountEnabled = $AzUser.AccountEnabled DirectoryScope = $DirectoryScope DurationInMonths = $DurationInMonths MemberType = $RoleMember.MemberType AccountType = $RoleMember.AccountType StartDateTime = $StartDateTime } } if ($PSBoundParameters.ContainsKey('HideActivatedRoles')) { $Output | Sort-Object PimAssignment, AzureADRole | Where-Object {$_.PimAssignment -ne 'PimActivated'} } else { $Output | Sort-Object PimAssignment, AzureADRole } } } catch { Write-Error $_.Exception.Message } } } #end userid parameter set if ($PSBoundParameters.ContainsKey('RoleName')) { try { [System.Collections.Generic.List[Object]]$RoleMemberList = @() if ($PSBoundParameters.ContainsKey('PimAssignment')) { if ($PSBoundParameters.ContainsValue('Active')) { $AssignmentList = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter "RoleDefinitionId eq '$($RoleHash[$RoleName])'" -ExpandProperty Principal,DirectoryScope -All $AssignmentList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value "Active" -Force -PassThru | Out-Null $AssignmentList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties."@odata.type".split('.')[2] } -Force -PassThru | Out-Null $AssignmentList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null} } if ($PSBoundParameters.ContainsValue('Eligibile')) { $EligibleList = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter "RoleDefinitionId eq '$($RoleHash[$RoleName])'" -ExpandProperty Principal,DirectoryScope -All $EligibleList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value "Eligibile" -Force -PassThru | Out-Null $EligibleList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties."@odata.type".split('.')[2] } -Force -PassThru | Out-Null $EligibleList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null} } } else { $AssignmentList = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter "RoleDefinitionId eq '$($RoleHash[$RoleName])'" -ExpandProperty Principal,DirectoryScope -All $AssignmentList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value "Active" -Force -PassThru | Out-Null $AssignmentList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties."@odata.type".split('.')[2] } -Force -PassThru | Out-Null $EligibleList = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter "RoleDefinitionId eq '$($RoleHash[$RoleName])'" -ExpandProperty Principal,DirectoryScope -All $EligibleList | Add-Member -MemberType NoteProperty -Name AssignmentScope -Value "Eligibile" -Force -PassThru | Out-Null $EligibleList | Add-Member -MemberType ScriptProperty -Name AccountType -Value {$this.Principal.AdditionalProperties."@odata.type".split('.')[2] } -Force -PassThru | Out-Null $AssignmentList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null} $EligibleList | ForEach-Object {$RoleMemberList.Add($_) | Out-Null} } if ($RoleMemberList) { $Output = foreach ($RoleMember in $RoleMemberList) { if ($RoleMember.DirectoryScopeId -eq '/') { $DirectoryScope = 'Global' } elseif ($RoleMember.DirectoryScopeId -match 'administrativeUnits') { $DirectoryScope = $RoleMember.DirectoryScope.AdditionalProperties.displayName } else { $DirectoryScope = 'Unknown' } if ($RoleMember.ScheduleInfo.Expiration.Type -eq 'noExpiration') { $DurationInMonths = 'Permanent' $EndDate = 'Permanent' } else { $Days = ($RoleMember.ScheduleInfo.Expiration.EndDateTime) - ($RoleMember.ScheduleInfo.StartDateTime) | select -ExpandProperty TotalDays $DurationInMonths = $Days / 30.4167 -as [int] $EndDate = (Get-Date $RoleMember.ScheduleInfo.Expiration.EndDateTime)#.ToString('yyyy-MM-dd') } if ($RoleMember.AssignmentScope -eq 'Active' -and $RoleMember.AssignmentType -eq 'Activated') { $AssignmentScope = 'PimActivated' } else { $AssignmentScope = $RoleMember.AssignmentScope } if ($RoleMember.ScheduleInfo.StartDateTime -and $RoleMember.CreatedDateTime) { $StartDateTime = (Get-Date $RoleMember.ScheduleInfo.StartDateTime).ToLocalTime() } else { $StartDateTime = (Get-Date 1/1/1999 -Hour 0 -Minute 0 -Millisecond 0) } switch ($RoleMember.AccountType) { 'User' { [PSCustomObject]@{ UserPrincipalName = $RoleMember.Principal.AdditionalProperties.userPrincipalName AzureADRole = $RoleHash[$RoleMember.RoleDefinitionId] PimAssignment = $AssignmentScope EndDateTime = $EndDate AccountEnabled = $RoleMember.Principal.AdditionalProperties.accountEnabled DirectoryScope = $DirectoryScope DurationInMonths = $DurationInMonths MemberType = $RoleMember.MemberType AccountType = $RoleMember.AccountType StartDateTime = $StartDateTime } } 'Group' { $GroupMemberList = Get-MgGroupTransitiveMember -GroupId $RoleMember.PrincipalId foreach ($GroupMember in $GroupMemberList) { [PSCustomObject]@{ UserPrincipalName = $GroupMember.AdditionalProperties.userPrincipalName AzureADRole = $RoleHash[$RoleMember.RoleDefinitionId] PimAssignment = $AssignmentScope EndDateTime = $EndDate AccountEnabled = $GroupMember.AdditionalProperties.accountEnabled DirectoryScope = $DirectoryScope DurationInMonths = $DurationInMonths MemberType = $RoleMember.MemberType AccountType = $GroupMember.AdditionalProperties.'@odata.type'.Split('.')[2] StartDateTime = $StartDateTime } } } 'servicePrincipal' { [PSCustomObject]@{ UserPrincipalName = $RoleMember.Principal.additionalproperties.displayName AzureADRole = $RoleHash[$RoleMember.RoleDefinitionId] PimAssignment = $AssignmentScope EndDateTime = $EndDate AccountEnabled = $RoleMember.Principal.AdditionalProperties.accountEnabled DirectoryScope = $DirectoryScope DurationInMonths = $DurationInMonths MemberType = $RoleMember.MemberType AccountType = $RoleMember.AccountType StartDateTime = $StartDateTime } } } } if ($PSBoundParameters.ContainsKey('HideActivatedRoles')) { $Output | Sort-Object PimAssignment, AzureADRole | Where-Object {$_.PimAssignment -ne 'PimActivated'} } else { $Output | Sort-Object PimAssignment, AzureADRole } } } catch { Write-Error $_.Exception.Message } } #end rolename parameter set } END {} }
Script Parameters
When using this script, you can use the following parameters to customize the output. Let’s go over those now.
-UserId
DataType: string
Description: Specify the UserId or UserPrincipalName of the principal you want to find active or eligible roles.
-RoleName
DataType: string
Description: Specify the Entra ID Role name to get all principals that are assigned that role.
-PimAssignment
DataType: string
Description: Specify either Active or Eligible to display those results..
-TenantId
DataType: string
Description: Specify the tenant Id to query that specific tenant. You must be authenticated to that tenant.
-HideActivatedRoles
DataType: switch
Description: When used, the results will hide all PIM activated roles.
Example 1: Specifying the UserId parameter
Get-MgPimRoleAssignment -UserId [email protected] UserPrincipalName : [email protected] AzureADRole : Helpdesk Administrator PimAssignment : Eligibile EndDateTime : Permanent AccountEnabled : True DirectoryScope : Admin Unit DurationInMonths : Permanent MemberType : Direct AccountType : user StartDateTime : 2/19/2024 3:34:32 PM
Example 2: Specifying the RoleName parameter that are eligible
Get-MgPimRoleAssignment -RoleName 'Helpdesk administrator' -PimAssignment Eligibile UserPrincipalName : [email protected] AzureADRole : Helpdesk Administrator PimAssignment : Eligibile EndDateTime : Permanent AccountEnabled : True DirectoryScope : Global DurationInMonths : Permanent MemberType : Direct AccountType : user StartDateTime : 2/19/2024 3:43:16 PM UserPrincipalName : [email protected] AzureADRole : Helpdesk Administrator PimAssignment : Eligibile EndDateTime : Permanent AccountEnabled : True DirectoryScope : Admin Unit DurationInMonths : Permanent MemberType : Direct AccountType : user StartDateTime : 2/19/2024 3:34:32 PM
Conclusion
Hopefully this article was able to help you get Entra ID PIM Role Assignment Using Graph API. With this script, you should be able to get all active, eligible AND eligible assignments that have been activated.
What have i missed here Paul?
Error: PS C:\Windows\System32> Get-MgPimRoleAssignment -RoleName ‘Global Administrator’
InvalidOperation: The property ‘Scopes’ cannot be found on this object. Verify that the property exists and can be set.
another error
PS C:\Windows\System32> Get-MgPimRoleAssignment -Verbose
VERBOSE: GET https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions with 0-byte payload
Invoke-MgGraphRequest: InteractiveBrowserCredential authentication failed: AADSTS650053: The application ‘Microsoft Graph Command Line Tools’
asked for scope ‘Policy.Read.AuthenticationMethod’ that doesn’t exist on the resource
‘00000003-0000-0000-c000-000000000000’. Contact the app vendor. Trace ID: 06b08658-cc2a-47b9-8206-b949cae25e00
Correlation ID: bc224d57-e9b6-4ff5-884c-c3c94b769878 Timestamp: 2024-03-21 03:41:57Z
This is a bug in the script.
There is no scope named ‘Policy.Read.AuthenticationMethod’, the proper scope name is ‘Policy.ReadWrite.AuthenticationMethod’.
This is the line causing the error:
$ConnectionGraph.Scopes = $ConnectionGraph.Scopes -replace “write”,”” | select -Unique
It appears that what the author was trying to do was simplify the next block of code, but he failed to consider that some scopes may require the “write” section. Just comment the line out. (Add a ‘#’ in front of the ‘$’.) That worked for me.
Error:
PS C:\temp> Get-MgPimRoleAssignment -RoleName ‘Global Administrator’
InvalidOperation: The property ‘Scopes’ cannot be found on this object. Verify that the property exists and can be set.
What is the minimum Scope or rights we need to run this?
I mean Connect-MgGraph -Scopes “minimum-rights” -tenantID 1234
Post/Script has been updated for scopes.