6

Get Entra ID PIM Role Assignment Using Graph API

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.
 

 

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
    • –OR–

    • RoleEligibilitySchedule.Read.Directory
    • RoleAssignmentSchedule.Read.Directory
    • RoleManagement.Read.Directory

 

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

Pim Role assignment with userid parameter
 

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

Pim Role assignment with rolename parameter
 

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.
 

5/5 - (1 vote)

Paul Contreras

Hi, my name is Paul and I am a Sysadmin who enjoys working on various technologies from Microsoft, VMWare, Cisco and many others. Join me as I document my trials and tribulations of the daily grind of System Administration.

6 Comments

  1. 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.

  2. 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.

  3. 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.

  4. What is the minimum Scope or rights we need to run this?
    I mean Connect-MgGraph -Scopes “minimum-rights” -tenantID 1234

Leave a Reply

Your email address will not be published. Required fields are marked *