AzurePIMStuff.psm1

function Get-PIMAccountEligibleMemberOf {
    <#
    .SYNOPSIS
    Function returns groups where selected account(s) is eligible (via PIM) as a member.
 
    .DESCRIPTION
    Function returns groups where selected account(s) is eligible (via PIM) as a member.
 
    .PARAMETER id
    Object ID of the account(s) you want to process.
 
    .PARAMETER transitive
    Switch to return not just direct PIM groups where processed account is member of, but also do the same for the PIM groups recursively.
 
    .PARAMETER onlyMembers
    Switch to return just list of groups processed account is member of.
    Not the object with 'Id', 'MemberOf' properties.
 
    .PARAMETER PIMGroupList
    List of PIM groups and their eligible assignments.
    Can be retrieved via Get-PIMGroup.
    Used internally for recursive function calls.
 
    .PARAMETER includePermanentMembership
    Switch to include non-PIM groups in the output.
    Account can be eligible member of PIM group A, and such group can be permanent member of ordinary group B and eligible member of PIM group C. With this switch A, B and C will be returned instead of just A and C.
 
    .EXAMPLE
    Get-PIMAccountEligibleMemberOf -id 877f3913-cb92-4a2d-af33-4b20efb50e54, 9048d9ba-59d1-451b-8764-88b034612fd9
 
    Get PIM groups where selected accounts are direct members.
 
    .EXAMPLE
    Get-PIMAccountEligibleMemberOf -id 877f3913-cb92-4a2d-af33-4b20efb50e54, 9048d9ba-59d1-451b-8764-88b034612fd9 -transitive
 
    Get PIM groups where selected accounts are members (direct or indirect via another PIM group).
    #>


    [Alias("Get-AzureAccountEligibleMemberOf")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [guid[]] $id,

        [switch] $transitive,

        [switch] $onlyMembers,

        [switch] $includePermanentMembership,

        $PIMGroupList
    )

    #region checks
    if (!(Get-Command Get-MgContext -ErrorAction silentlycontinue) -or !(Get-MgContext)) {
        throw "$($MyInvocation.MyCommand): Authentication needed. Please call Connect-MgGraph."
    }

    if ($id.count -gt 1 -and $onlyMembers) {
        Write-Warning "All groups given accounts are member of will be outputted at once"
    }

    if ($includePermanentMembership -and !$transitive) {
        Write-Error "'includePermanentMembership' can be used only with the 'transitive'"
    }
    #endregion checks

    if (!$PIMGroupList) {
        $PIMGroupList = Get-PIMGroup
    }

    # no PIM group exist, exit
    if (!$PIMGroupList) {
        if ($onlyMembers) {
            return
        } else {
            $id | % {
                [PSCustomObject]@{
                    Id       = $_
                    MemberOf = $null
                }
            }

            return
        }
    }

    foreach ($accountId in $id) {
        [System.Collections.Generic.List[object]] $memberOf = @()

        # get PIM groups where account in question is eligible as a member
        $PIMGroupList | ? { $_.EligibleAssignment.AccessId -eq 'member' -and $_.EligibleAssignment.PrincipalId -eq $accountId } | % {
            Write-Verbose "Account $accountId is eligible member of the group $($_.Id)"
            $memberOf.Add($_)
        }

        # get PIM groups where groups account in question is eligible as a member are also eligible as members for other PIM groups
        if ($transitive -and $memberOf.Id) {
            Write-Verbose "Getting eligible memberof recursively for group(s): $($memberOf.Id -join ', ')"

            Get-PIMAccountEligibleMemberOf -id $memberOf.Id -transitive -onlyMembers -PIMGroupList $PIMGroupList -includePermanentMembership:$includePermanentMembership -Verbose:$VerbosePreference | % {
                $memberOf.Add($_)
            }

            if ($includePermanentMembership) {
                Write-Verbose "Getting permanent memberof recursively for group(s): $($memberOf.Id -join ', ')"

                Get-AzureDirectoryObjectMemberOf -id $memberOf.Id -Verbose:$VerbosePreference | % {
                    $memberOf.Add($_.MemberOf)
                }
            }
        }

        if ($onlyMembers) {
            $memberOf
        } else {
            [PSCustomObject]@{
                Id       = $accountId
                MemberOf = $memberOf
            }
        }
    }
}

function Get-PIMDirectoryRoleAssignmentSetting {
    <#
    .SYNOPSIS
    Gets PIM assignment settings for a given Azure AD directory role.
 
    .DESCRIPTION
    This function retrieves Privileged Identity Management (PIM) policy assignment settings for a specified Azure AD directory role, including activation duration, enablement rules, approval requirements, notification settings, and more. You can specify the role by name or ID.
 
    .PARAMETER roleName
    The display name of the Azure AD directory role to query. Mandatory if using the roleName parameter set.
 
    .PARAMETER roleId
    The object ID of the Azure AD directory role to query. Mandatory if using the roleId parameter set.
 
    .EXAMPLE
    Get-PIMDirectoryRoleAssignmentSetting -roleName "Global Administrator"
    Retrieves PIM assignment settings for the Global Administrator role.
 
    .EXAMPLE
    Get-PIMDirectoryRoleAssignmentSetting -roleId "12345678-aaaa-bbbb-cccc-1234567890ab"
    Retrieves PIM assignment settings for the specified role ID.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = "roleName")]
        [string] $roleName,

        [Parameter(Mandatory = $true, ParameterSetName = "roleId")]
        [string] $roleId
    )

    if (!(Get-Command Get-MgContext -ErrorAction silentlycontinue) -or !(Get-MgContext)) {
        throw "$($MyInvocation.MyCommand): Authentication needed. Please call Connect-MgGraph."
    }

    if ($roleName) {
        $response = Invoke-MgGraphRequest -Uri "v1.0/roleManagement/directory/roleDefinitions?`$filter=displayname eq '$roleName'" | Get-MgGraphAllPages
        $roleID = $response.Id
        Write-Verbose "roleID = $roleID"
        if (!$roleID) {
            throw "Role $roleName not found. Search is CASE SENSITIVE!"
        }
    }

    # get PIM policyID for that role
    $response = Invoke-MgGraphRequest -Uri "v1.0/policies/roleManagementPolicyAssignments?`$filter=scopeType eq 'DirectoryRole' and roleDefinitionId eq '$roleID' and scopeId eq '/' " | Get-MgGraphAllPages
    $policyID = $response.policyID
    Write-Verbose "policyID = $policyID"

    # get the rules
    $response = Invoke-MgGraphRequest -Uri "v1.0/policies/roleManagementPolicies/$policyID/rules" | Get-MgGraphAllPages

    # Maximum end user activation duration in Hour (PT24H) // Max 24H in portal but can be greater
    $_activationDuration = $($response | Where-Object { $_.id -eq "Expiration_EndUser_Assignment" }).maximumDuration # | Select-Object -ExpandProperty maximumduration
    # End user enablement rule (MultiFactorAuthentication, Justification, Ticketing)
    $_enablementRules = $($response | Where-Object { $_.id -eq "Enablement_EndUser_Assignment" }).enabledRules
    # Active assignment requirement
    $_activeAssignmentRequirement = $($response | Where-Object { $_.id -eq "Enablement_Admin_Assignment" }).enabledRules
    # Authentication context
    $_authenticationContext_Enabled = $($response | Where-Object { $_.id -eq "AuthenticationContext_EndUser_Assignment" }).isEnabled
    $_authenticationContext_value = $($response | Where-Object { $_.id -eq "AuthenticationContext_EndUser_Assignment" }).claimValue

    # approval required
    $_approvalrequired = $($response | Where-Object { $_.id -eq "Approval_EndUser_Assignment" }).setting.isapprovalrequired
    # approvers
    $approvers = $($response | Where-Object { $_.id -eq "Approval_EndUser_Assignment" }).setting.approvalStages.primaryApprovers
    if (( $approvers | Measure-Object | Select-Object -ExpandProperty Count) -gt 0) {
        $approvers | ForEach-Object {
            if ($_."@odata.type" -eq "#microsoft.graph.groupMembers") {
                $_.userType = "group"
                $_.id = $_.groupID
            } else {
                #"@odata.type": "#microsoft.graph.singleUser",
                $_.userType = "user"
                $_.id = $_.userID
            }

            $_approvers += '@{"id"="' + $_.id + '";"description"="' + $_.description + '";"userType"="' + $_.userType + '"},'
        }
    }

    # permanent assignmnent eligibility
    $_eligibilityExpirationRequired = $($response | Where-Object { $_.id -eq "Expiration_Admin_Eligibility" }).isExpirationRequired
    if ($_eligibilityExpirationRequired -eq "true") {
        $_permanentEligibility = "false"
    } else {
        $_permanentEligibility = "true"
    }
    # maximum assignment eligibility duration
    $_maxAssignmentDuration = $($response | Where-Object { $_.id -eq "Expiration_Admin_Eligibility" }).maximumDuration

    # permanent activation
    $_activeExpirationRequired = $($response | Where-Object { $_.id -eq "Expiration_Admin_Assignment" }).isExpirationRequired
    if ($_activeExpirationRequired -eq "true") {
        $_permanentActiveAssignment = "false"
    } else {
        $_permanentActiveAssignment = "true"
    }
    # maximum activation duration
    $_maxActiveAssignmentDuration = $($response | Where-Object { $_.id -eq "Expiration_Admin_Assignment" }).maximumDuration

    # Notification Eligibility Alert (Send notifications when members are assigned as eligible to this role)
    $_Notification_Admin_Admin_Eligibility = $response | Where-Object { $_.id -eq "Notification_Admin_Admin_Eligibility" }
    # Notification Eligibility Assignee (Send notifications when members are assigned as eligible to this role: Notification to the assigned user (assignee))
    $_Notification_Eligibility_Assignee = $response | Where-Object { $_.id -eq "Notification_Requestor_Admin_Eligibility" }
    # Notification Eligibility Approvers (Send notifications when members are assigned as eligible to this role: request to approve a role assignment renewal/extension)
    $_Notification_Eligibility_Approvers = $response | Where-Object { $_.id -eq "Notification_Approver_Admin_Eligibility" }

    # Notification Active Assignment Alert (Send notifications when members are assigned as active to this role)
    $_Notification_Active_Alert = $response | Where-Object { $_.id -eq "Notification_Admin_Admin_Assignment" }
    # Notification Active Assignment Assignee (Send notifications when members are assigned as active to this role: Notification to the assigned user (assignee))
    $_Notification_Active_Assignee = $response | Where-Object { $_.id -eq "Notification_Requestor_Admin_Assignment" }
    # Notification Active Assignment Approvers (Send notifications when members are assigned as active to this role: Request to approve a role assignment renewal/extension)
    $_Notification_Active_Approvers = $response | Where-Object { $_.id -eq "Notification_Approver_Admin_Assignment" }

    # Notification Role Activation Alert (Send notifications when eligible members activate this role: Role activation alert)
    $_Notification_Activation_Alert = $response | Where-Object { $_.id -eq "Notification_Admin_EndUser_Assignment" }
    # Notification Role Activation Assignee (Send notifications when eligible members activate this role: Notification to activated user (requestor))
    $_Notification_Activation_Assignee = $response | Where-Object { $_.id -eq "Notification_Requestor_EndUser_Assignment" }
    # Notification Role Activation Approvers (Send notifications when eligible members activate this role: Request to approve an activation)
    $_Notification_Activation_Approver = $response | Where-Object { $_.id -eq "Notification_Approver_EndUser_Assignment" }


    [PSCustomObject]@{
        RoleName                                                     = $roleName
        RoleID                                                       = $roleID
        PolicyID                                                     = $policyId
        ActivationDuration                                           = $_activationDuration
        EnablementRules                                              = $_enablementRules -join ','
        ActiveAssignmentRequirement                                  = $_activeAssignmentRequirement -join ','
        AuthenticationContext_Enabled                                = $_authenticationContext_Enabled
        AuthenticationContext_Value                                  = $_authenticationContext_value
        ApprovalRequired                                             = $_approvalrequired
        Approvers                                                    = $_approvers -join ','
        AllowPermanentEligibleAssignment                             = $_permanentEligibility
        MaximumEligibleAssignmentDuration                            = $_maxAssignmentDuration
        AllowPermanentActiveAssignment                               = $_permanentActiveAssignment
        MaximumActiveAssignmentDuration                              = $_maxActiveAssignmentDuration
        Notification_Eligibility_Alert_isDefaultRecipientEnabled     = $($_Notification_Admin_Admin_Eligibility.isDefaultRecipientsEnabled)
        Notification_Eligibility_Alert_NotificationLevel             = $($_Notification_Admin_Admin_Eligibility.notificationLevel)
        Notification_Eligibility_Alert_Recipients                    = $($_Notification_Admin_Admin_Eligibility.notificationRecipients) -join ','
        Notification_Eligibility_Assignee_isDefaultRecipientEnabled  = $($_Notification_Eligibility_Assignee.isDefaultRecipientsEnabled)
        Notification_Eligibility_Assignee_NotificationLevel          = $($_Notification_Eligibility_Assignee.NotificationLevel)
        Notification_Eligibility_Assignee_Recipients                 = $($_Notification_Eligibility_Assignee.notificationRecipients) -join ','
        Notification_Eligibility_Approvers_isDefaultRecipientEnabled = $($_Notification_Eligibility_Approvers.isDefaultRecipientsEnabled)
        Notification_Eligibility_Approvers_NotificationLevel         = $($_Notification_Eligibility_Approvers.NotificationLevel)
        Notification_Eligibility_Approvers_Recipients                = $($_Notification_Eligibility_Approvers.notificationRecipients -join ',')
        Notification_Active_Alert_isDefaultRecipientEnabled          = $($_Notification_Active_Alert.isDefaultRecipientsEnabled)
        Notification_Active_Alert_NotificationLevel                  = $($_Notification_Active_Alert.notificationLevel)
        Notification_Active_Alert_Recipients                         = $($_Notification_Active_Alert.notificationRecipients -join ',')
        Notification_Active_Assignee_isDefaultRecipientEnabled       = $($_Notification_Active_Assignee.isDefaultRecipientsEnabled)
        Notification_Active_Assignee_NotificationLevel               = $($_Notification_Active_Assignee.notificationLevel)
        Notification_Active_Assignee_Recipients                      = $($_Notification_Active_Assignee.notificationRecipients -join ',')
        Notification_Active_Approvers_isDefaultRecipientEnabled      = $($_Notification_Active_Approvers.isDefaultRecipientsEnabled)
        Notification_Active_Approvers_NotificationLevel              = $($_Notification_Active_Approvers.notificationLevel)
        Notification_Active_Approvers_Recipients                     = $($_Notification_Active_Approvers.notificationRecipients -join ',')
        Notification_Activation_Alert_isDefaultRecipientEnabled      = $($_Notification_Activation_Alert.isDefaultRecipientsEnabled)
        Notification_Activation_Alert_NotificationLevel              = $($_Notification_Activation_Alert.NotificationLevel)
        Notification_Activation_Alert_Recipients                     = $($_Notification_Activation_Alert.NotificationRecipients -join ',')
        Notification_Activation_Assignee_isDefaultRecipientEnabled   = $($_Notification_Activation_Assignee.isDefaultRecipientsEnabled)
        Notification_Activation_Assignee_NotificationLevel           = $($_Notification_Activation_Assignee.NotificationLevel)
        Notification_Activation_Assignee_Recipients                  = $($_Notification_Activation_Assignee.NotificationRecipients -join ',')
        Notification_Activation_Approver_isDefaultRecipientEnabled   = $($_Notification_Activation_Approver.isDefaultRecipientsEnabled)
        Notification_Activation_Approver_NotificationLevel           = $($_Notification_Activation_Approver.NotificationLevel)
        Notification_Activation_Approver_Recipients                  = $($_Notification_Activation_Approver.NotificationRecipients -join ',')
    }
}

function Get-PIMDirectoryRoleEligibleAssignment {
    <#
    .SYNOPSIS
    Function returns Azure Directory role eligible assignments.
 
    .DESCRIPTION
    Function returns Azure Directory role eligible assignments.
 
    .PARAMETER skipAssignmentSettings
    If specified, the function will not retrieve assignment settings for the roles. This can speed up the function if you don't need the detailed settings.
 
    .EXAMPLE
    Get-PIMDirectoryRoleEligibleAssignment
    #>


    [CmdletBinding()]
    param (
        [switch] $skipAssignmentSettings
    )

    if (!(Get-Command Get-MgContext -ErrorAction silentlycontinue) -or !(Get-MgContext)) {
        throw "$($MyInvocation.MyCommand): Authentication needed. Please call Connect-MgGraph."
    }

    Invoke-MgGraphRequest -Uri "v1.0/roleManagement/directory/roleEligibilityScheduleInstances?`$expand=roleDefinition,principal" | Get-MgGraphAllPages | % {
        if ($skipAssignmentSettings) {
            $_ | select *, @{n = 'PrincipalName'; e = { $_.principal.displayName } }, @{n = 'RoleName'; e = { $_.roleDefinition.displayName } }
        } else {
            $rules = Get-PIMDirectoryRoleAssignmentSetting -roleId $_.roleDefinitionId

            $_ | select *, @{n = 'PrincipalName'; e = { $_.principal.displayName } }, @{n = 'RoleName'; e = { $_.roleDefinition.displayName } }, @{n = 'Policy'; e = { $rules } }
        }
    }
}

function Get-PIMGroup {
    <#
    .SYNOPSIS
    Function returns Azure groups with some PIM eligible assignments a.k.a. PIM enabled groups.
 
    .DESCRIPTION
    Function returns Azure groups with some PIM eligible assignments a.k.a. PIM enabled groups.
 
    To get more details about the PIM eligible assignments, use Get-PIMGroupEligibleAssignment.
 
    .EXAMPLE
    Get-PIMGroup
 
    Function returns Azure groups with some PIM eligible assignments a.k.a. PIM enabled groups.
    #>


    [CmdletBinding()]
    param()

    Write-Warning "Searching for groups with PIM eligible assignment. This can take a while."

    if (!(Get-Command Get-MgContext -ErrorAction silentlycontinue) -or !(Get-MgContext)) {
        throw "$($MyInvocation.MyCommand): Authentication needed. Please call Connect-MgGraph."
    }

    # I don't know how to get PIM enabled groups in better way than just get all PIM capable groups and find whether they have some eligible assignment set
    $possiblePIMGroup = Invoke-MgGraphRequest -Uri "v1.0/groups?`$select=Id,DisplayName,OnPremisesSyncEnabled,GroupTypes,MailEnabled,SecurityEnabled" | Get-MgGraphAllPages | ? { $_.onPremisesSyncEnabled -eq $null -and $_.GroupTypes -notcontains 'DynamicMembership' -and !($_.MailEnabled -and $_.SecurityEnabled -and $_.GroupTypes -notcontains 'Unified') -and !($_.MailEnabled -and !$_.SecurityEnabled) }

    if (!$possiblePIMGroup) { return }

    $groupWithPIMEligibleAssignment = New-GraphBatchRequest -url "identityGovernance/privilegedAccess/group/eligibilitySchedules?`$filter=groupId eq '<placeholder>'" -placeholder $possiblePIMGroup.Id | Invoke-GraphBatchRequest -graphVersion v1.0 -dontAddRequestId

    $possiblePIMGroup | ? Id -In ($groupWithPIMEligibleAssignment.groupId) | select *, @{Name = 'EligibleAssignment'; Expression = { $id = $_.Id; $groupWithPIMEligibleAssignment | ? groupId -EQ $id } }
}

function Get-PIMGroupEligibleAssignment {
    <#
    .SYNOPSIS
    Returns eligible assignments for Azure AD groups (PIM).
 
    .DESCRIPTION
    This function finds Azure AD groups that have Privileged Identity Management (PIM) eligible assignments. It can process specific group IDs or search all groups for PIM eligibility. Optionally, it retrieves assignment settings for each group and translates object IDs to display names for easier interpretation.
 
    .PARAMETER groupId
    One or more Azure AD group IDs to process. If not specified, all possible PIM-enabled groups will be searched.
 
    .PARAMETER skipAssignmentSettings
    If specified, the function will not retrieve assignment settings for the roles. This can speed up the function if you don't need the detailed settings.
 
    .EXAMPLE
    Get-PIMGroupEligibleAssignment
    Returns all Azure AD groups with PIM eligible assignments and their assignment settings.
 
    .EXAMPLE
    Get-PIMGroupEligibleAssignment -groupId "12345678-aaaa-bbbb-cccc-1234567890ab" -skipAssignmentSettings
    Returns PIM eligible assignments for the specified group, skipping assignment settings for faster results.
 
    .NOTES
    Author: @AndrewZtrhgf
    #>


    [CmdletBinding()]
    param (
        [string[]] $groupId,

        [switch] $skipAssignmentSettings
    )

    if (!(Get-Command Get-MgContext -ErrorAction silentlycontinue) -or !(Get-MgContext)) {
        throw "$($MyInvocation.MyCommand): Authentication needed. Please call Connect-MgGraph."
    }

    if ($groupId) {
        $possiblePIMGroupId = $groupId
    } else {
        # I don't know how to get the list of PIM enabled groups so I have to find them
        # by searching for eligible role assignments for every PIM-supported-type group
        Write-Warning "Searching for groups with PIM eligible assignment. This can take a while."

        $possiblePIMGroupId = (Invoke-MgGraphRequest -Uri "v1.0/groups?`$select=Id,DisplayName,OnPremisesSyncEnabled,GroupTypes,MailEnabled,SecurityEnabled" | Get-MgGraphAllPages | ? { $_.onPremisesSyncEnabled -eq $null -and $_.GroupTypes -notcontains 'DynamicMembership' -and !($_.MailEnabled -and $_.SecurityEnabled -and $_.GroupTypes -notcontains 'Unified') -and !($_.MailEnabled -and !$_.SecurityEnabled) }).id
    }

    if (!$possiblePIMGroupId) { return }

    # search for groups that have some PIM settings defined
    $groupWithPIMEligibleAssignment = New-GraphBatchRequest -url "identityGovernance/privilegedAccess/group/eligibilitySchedules?`$filter=groupId eq '<placeholder>'" -placeholder $possiblePIMGroupId | Invoke-GraphBatchRequest -graphVersion v1.0 -dontAddRequestId

    if (!$groupWithPIMEligibleAssignment) {
        Write-Warning "None of the groups have PIM eligible assignments"
        return
    }

    #region get assignment settings for all PIM groups
    if (!$skipAssignmentSettings) {
        $assignmentSettingBatch = [System.Collections.Generic.List[Object]]::new()
        $groupWithPIMEligibleAssignment | % {
            $groupId = $_.groupId
            $type = $_.accessId

            $assignmentSettingBatch.Add((New-GraphBatchRequest -url "policies/roleManagementPolicyAssignments?`$filter=scopeId eq '$groupId' and scopeType eq 'Group' and roleDefinitionId eq '$type'&`$expand=policy(`$expand=rules)"))
        }

        $assignmentSettingList = Invoke-GraphBatchRequest -batchRequest $assignmentSettingBatch -graphVersion beta -dontAddRequestId
    }
    #endregion get assignment settings for all PIM groups

    #region translate all Ids to corresponding DisplayName
    $idToTranslate = [System.Collections.Generic.List[Object]]::new()
    $groupWithPIMEligibleAssignment.PrincipalId | % { $idToTranslate.add($_) }
    $groupWithPIMEligibleAssignment.groupId | % { $idToTranslate.add($_) }
    $idToTranslate = $idToTranslate | select -Unique
    $translationList = Get-AzureDirectoryObject -id $idToTranslate
    #endregion translate all Ids to corresponding DisplayName

    # output the results
    $groupWithPIMEligibleAssignment | % {
        $groupId = $_.groupId
        $type = $_.accessId
        $principalId = $_.PrincipalId

        # get the PIM assignment settings
        if ($skipAssignmentSettings) {
            $assignmentSetting = $null
        } else {
            $assignmentSetting = $assignmentSettingList | ? { $_.ScopeId -eq $groupId -and $_.roleDefinitionId -eq $type }
        }

        $_ | select Id, CreatedDateTime, ModifiedDateTime, Status, GroupId, @{n = 'GroupName'; e = { ($translationList | ? Id -EQ $groupId).DisplayName } }, PrincipalId, @{n = 'PrincipalName'; e = { ($translationList | ? Id -EQ $principalId).DisplayName } }, AccessId, MemberType, ScheduleInfo, @{ n = 'Policy'; e = { $assignmentSetting.policy } }
    }
}

function Get-PIMManagementGroupEligibleAssignment {
    <#
    .SYNOPSIS
    Function returns all PIM eligible IAM assignments on selected (all) Azure Management group(s).
 
    .DESCRIPTION
    Function returns all PIM eligible IAM assignments on selected (all) Azure Management group(s).
 
    .PARAMETER name
    Name of the Azure Management Group(s) to process.
 
    .PARAMETER skipAssignmentSettings
    If specified, the function will not retrieve assignment settings for the roles. This can speed up the function if you don't need the detailed settings.
 
    .EXAMPLE
    Get-PIMManagementGroupEligibleAssignment
 
    Returns all PIM eligible IAM assignments over all Azure Management Groups.
 
    .EXAMPLE
    Get-PIMManagementGroupEligibleAssignment -Name IT_test
 
    Returns all PIM eligible IAM assignments over selected Azure Management Group.
    #>


    [CmdletBinding()]
    param (
        [string[]] $name,

        [switch] $skipAssignmentSettings
    )

    if (!(Get-Command 'Get-AzAccessToken' -ErrorAction silentlycontinue) -or !($azAccessToken = Get-AzAccessToken -WarningAction SilentlyContinue -ErrorAction SilentlyContinue) -or $azAccessToken.ExpiresOn -lt [datetime]::now) {
        throw "$($MyInvocation.MyCommand): Authentication needed. Please call Connect-AzAccount."
    }

    if ($name) {
        $managementGroupNameList = $name
    } else {
        $managementGroupNameList = (Get-AzManagementGroup).Name
    }

    New-AzureBatchRequest -url "https://management.azure.com/providers/Microsoft.Management/managementGroups/<placeholder>/providers/Microsoft.Authorization/roleEligibilitySchedules?api-version=2020-10-01&`$filter=atScope()" -placeholder $managementGroupNameList | Invoke-AzureBatchRequest | Expand-ObjectProperty -propertyName Properties | Expand-ObjectProperty -propertyName ExpandedProperties | ? memberType -EQ 'Direct' | % {
        if ($skipAssignmentSettings) {
            $assignmentSetting = $null
        } else {
            $roleId = ($_.roleDefinitionId -split "/")[-1]
            $assignmentSetting = Get-PIMResourceRoleAssignmentSetting -roleId $roleId -scope $_.Scope.Id
        }

        $_ | select *, @{n = 'Policy'; e = { $assignmentSetting } }
    }
}

function Get-PIMResourceRoleAssignmentSetting {
    <#
    .SYNOPSIS
    Gets PIM assignment settings for a given Azure resource role at a specific scope.
 
    .DESCRIPTION
    This function retrieves Privileged Identity Management (PIM) policy assignment settings for a specified Azure resource role (such as Reader, Contributor, etc.) at a given scope (subscription, resource group, or resource). You can specify the role by name or ID.
 
    .PARAMETER rolename
    The name of the Azure resource role to query. Mandatory if using the rolename parameter set.
 
    .PARAMETER roleId
    The object ID of the Azure resource role to query. Mandatory if using the roleId parameter set.
 
    .PARAMETER scope
    The Azure scope (subscription, resource group, or resource) to query for the role assignment settings. Mandatory.
 
    .EXAMPLE
    Get-PIMResourceRoleAssignmentSetting -rolename "Reader" -scope "/subscriptions/xxxx/resourceGroups/yyyy"
    Retrieves PIM assignment settings for the Reader role at the specified resource group scope.
 
    .EXAMPLE
    Get-PIMResourceRoleAssignmentSetting -roleId "acdd72a7-3385-48ef-bd42-f606fba81ae7" -scope "/subscriptions/xxxx/resourceGroups/yyyy"
    Retrieves PIM assignment settings for the specified role ID at the given scope.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = "rolename")]
        [string] $rolename,
        [Parameter(Mandatory = $true, ParameterSetName = "roleId")]
        [string] $roleId,
        [Parameter(Mandatory = $true)]
        [string] $scope
    )

    if (!(Get-Command 'Get-AzAccessToken' -ErrorAction silentlycontinue) -or !($azAccessToken = Get-AzAccessToken -WarningAction SilentlyContinue -ErrorAction SilentlyContinue) -or $azAccessToken.ExpiresOn -lt [datetime]::now) {
        throw "$($MyInvocation.MyCommand): Authentication needed. Please call Connect-AzAccount."
    }

    $base = "https://management.azure.com"
    $endpoint = "$base/$scope/providers/Microsoft.Authorization"
    # Get ID of the role $rolename assignable at the provided scope
    if ($rolename) {
        $restUri = "$endpoint/roleDefinitions?api-version=2022-04-01&`$filter=roleName eq '$rolename'"
    } else {
        $restUri = "$endpoint/roleDefinitions/$roleId?api-version=2022-04-01"
    }
    $roleID = ((Invoke-AzRestMethod -Uri $restUri -ErrorAction Stop).content | ConvertFrom-Json).value.id
    # get the role assignment for the roleID
    $restUri = "$endpoint/roleManagementPolicyAssignments?api-version=2020-10-01&`$filter=roleDefinitionId eq '$roleID'"
    $policyId = ((Invoke-AzRestMethod -Uri $restUri -ErrorAction Stop).content | ConvertFrom-Json).value.properties.policyId
    # get the role policy for the policyID
    $restUri = "$base/$policyId/?api-version=2020-10-01"
    ((Invoke-AzRestMethod -Uri $restUri -ErrorAction Stop).content | ConvertFrom-Json).properties
}

function Get-PIMSubscriptionEligibleAssignment {
    <#
    .SYNOPSIS
    Retrieves eligible role assignments for selected Azure subscriptions and their resources using PIM.
 
    .DESCRIPTION
    This function finds all Privileged Identity Management (PIM) eligible role assignments for the specified Azure subscriptions and their resources. If no subscription IDs are provided, it processes all enabled subscriptions in the tenant. The output includes principal, role, scope, and assignment details for each eligible assignment found.
 
    .PARAMETER id
    One or more Azure subscription IDs to process. If not provided, all enabled subscriptions will be processed automatically.
 
    .EXAMPLE
    Get-PIMSubscriptionEligibleAssignment
    Retrieves PIM eligible assignments for all enabled subscriptions and their resources.
 
    .EXAMPLE
    Get-PIMSubscriptionEligibleAssignment -id "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    Retrieves PIM eligible assignments for the specified subscription and its resources.
 
    #>


    [CmdletBinding()]
    param (
        [string[]] $id
    )

    if (!(Get-Command 'Get-AzAccessToken' -ErrorAction silentlycontinue) -or !($azAccessToken = Get-AzAccessToken -WarningAction SilentlyContinue -ErrorAction SilentlyContinue) -or $azAccessToken.ExpiresOn -lt [datetime]::now) {
        throw "$($MyInvocation.MyCommand): Authentication needed. Please call Connect-AzAccount."
    }

    if ($id) {
        $subscriptionId = $id
    } else {
        $subscriptionId = (Get-AzSubscription | ? State -EQ 'Enabled').Id
    }

    New-AzureBatchRequest -url "/subscriptions/<placeholder>/providers/Microsoft.Authorization/roleEligibilitySchedules?api-version=2020-10-01" -placeholder $subscriptionId | Invoke-AzureBatchRequest | ? { $_.Properties.MemberType -eq 'Direct' -and $_.Properties.ExpandedProperties.Scope.Type -ne "managementgroup" } | % {
        $id = $_.id

        $_.properties | % {
            if (!$_.endDateTime) { $end = "permanent" } else { $end = $_.endDateTime }

            [PSCustomObject] @{
                "PrincipalName"  = $_.expandedproperties.principal.displayName
                "PrincipalEmail" = $_.expandedproperties.principal.email
                "PrincipalType"  = $_.expandedproperties.principal.type
                "PrincipalId"    = $_.expandedproperties.principal.id
                "RoleName"       = $_.expandedproperties.roleDefinition.displayName
                "RoleType"       = $_.expandedproperties.roleDefinition.type
                "RoleId"         = $_.expandedproperties.roleDefinition.id
                "ScopeId"        = $_.expandedproperties.scope.id
                "ScopeName"      = $_.expandedproperties.scope.displayName
                "ScopeType"      = $_.expandedproperties.scope.type
                "Status"         = $_.Status
                "createdOn"      = $_.createdOn
                "startDateTime"  = $_.startDateTime
                "endDateTime"    = $end
                "updatedOn"      = $_.updatedOn
                "memberType"     = $_.memberType
                "id"             = $id
            }
        }
    }
}

Export-ModuleMember -function Get-PIMAccountEligibleMemberOf, Get-PIMDirectoryRoleAssignmentSetting, Get-PIMDirectoryRoleEligibleAssignment, Get-PIMGroup, Get-PIMGroupEligibleAssignment, Get-PIMManagementGroupEligibleAssignment, Get-PIMResourceRoleAssignmentSetting, Get-PIMSubscriptionEligibleAssignment

Export-ModuleMember -alias Get-AzureAccountEligibleMemberOf