Modules/Providers/ExportAADProvider.psm1
# Many of the commandlets can be replaced with direct API access, but we are starting the transition with the ones # below because they have slow imports that affect performance more than the others. Some commandlets are fast # and there is no obvoius performance advantage to using the API beyond maybe batching. $GraphEndpoints = @{ "Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleInstance" = "/beta/roleManagement/directory/roleEligibilityScheduleInstances" "Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleInstance" = "/beta/roleManagement/directory/roleAssignmentScheduleInstances" "Get-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleInstance" = "/beta/identityGovernance/privilegedAccess/group/eligibilityScheduleInstances" "Get-MgBetaPrivilegedAccessResource" = "/beta/privilegedAccess/aadGroups/resources" } function Invoke-GraphDirectly { param ( [ValidateNotNullOrEmpty()] [string] $commandlet, [ValidateNotNullOrEmpty()] [string] $M365Environment, [System.Collections.Hashtable] $queryParams ) Write-Debug "Replacing Cmdlet: $commandlet" try { $endpoint = $GraphEndpoints[$commandlet] } catch { Write-Error "The commandlet $commandlet can't be used with the Invoke-GraphDirectly function yet." } if ($M365Environment -eq "gcchigh") { $endpoint = "https://graph.microsoft.us" + $endpoint } elseif ($M365Environment -eq "dod") { $endpoint = "https://dod-graph.microsoft.us" + $endpoint } else { $endpoint = "https://graph.microsoft.com" + $endpoint } if ($queryParams) { # If query params are passed in, we augment the endpoint URI to include the params. $q = [System.Web.HttpUtility]::ParseQueryString([string]::Empty) foreach ($item in $queryParams.GetEnumerator()) { $q.Add($item.Key, $item.Value) } $uri = [System.UriBuilder]::new("", "", 443, $endpoint) $uri.Query = $q.ToString() $endpoint = $uri.ToString() } Write-Debug "Graph Api direct: $endpoint" $resp = Invoke-MgGraphRequest -ErrorAction Stop -Uri $endpoint return $resp.Value } function Export-AADProvider { <# .Description Gets the Azure Active Directory (AAD) settings that are relevant to the SCuBA AAD baselines using a subset of the modules under the overall Microsoft Graph PowerShell Module .Functionality Internal #> [CmdletBinding()] param ( [ValidateNotNullOrEmpty()] [string] $M365Environment ) Import-Module $PSScriptRoot/ProviderHelpers/CommandTracker.psm1 $Tracker = Get-CommandTracker # The below cmdlet covers ~ 8 policy checks that inspect conditional access policies $AllPolicies = $Tracker.TryCommand("Get-MgBetaIdentityConditionalAccessPolicy") Import-Module $PSScriptRoot/ProviderHelpers/AADConditionalAccessHelper.psm1 $CapHelper = Get-CapTracker $CapTableData = $CapHelper.ExportCapPolicies($AllPolicies) # Used in generating the CAP html in the report if ($CapTableData -eq "") { # Quick sanity check, did ExportCapPolicies return something? Write-Warning "Error parsing CAP data, empty json returned from ExportCapPolicies." $CapTableData = "[]" } try { # Final sanity check, did ExportCapPolicies return valid json? ConvertFrom-Json $CapTableData -ErrorAction "Stop" | Out-Null } catch { Write-Warning "Error parsing CAP data, invalid json returned from ExportCapPolicies." $CapTableData = "[]" } $AllPolicies = ConvertTo-Json -Depth 10 @($AllPolicies) $SubscribedSku = $Tracker.TryCommand("Get-MgBetaSubscribedSku") # Get a list of the tenant's provisioned service plans - used to see if the tenant has AAD premium p2 license required for some checks # The Rego looks at the service_plans in the JSON $ServicePlans = $SubscribedSku.ServicePlans | Where-Object -Property ProvisioningStatus -eq -Value "Success" #Obtains license information for tenant and total number of active users $LicenseInfo = $SubscribedSku | Select-Object -Property Sku*, ConsumedUnits, PrepaidUnits | ConvertTo-Json -Depth 3 if ($ServicePlans) { # The RequiredServicePlan variable is used so that PIM Cmdlets are only executed if the tenant has the premium license $RequiredServicePlan = $ServicePlans | Where-Object -Property ServicePlanName -eq -Value "AAD_PREMIUM_P2" # Get-PrivilegedUser provides a list of privileged users and their role assignments. if ($RequiredServicePlan) { # If the tenant has the premium license then we also include calls to PIM APIs $PrivilegedUsers = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$true; "M365Environment"=$M365Environment}) } else{ $PrivilegedUsers = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$false; "M365Environment"=$M365Environment}) } # The Converto-Json call below doesn't need to have the input wrapped in an # array (e.g, "ConvertTo-Json (@PrivilegedUsers)") because $PrivilegedUsers is # a dictionary, not an array, and ConvertTo-Json doesn't mess up dictionaries like it does arrays $PrivilegedUsers = $PrivilegedUsers | ConvertTo-Json # While ConvertTo-Json won't mess up a dict as described in the above comment, # on error, $TryCommand returns an empty list, not a dictionary. $PrivilegedUsers = if ($null -eq $PrivilegedUsers) {"{}"} else {$PrivilegedUsers} # Get-PrivilegedRole provides a list of security configurations for each privileged role and information about Active user assignments if ($RequiredServicePlan){ # If the tenant has the premium license then we also include calls to PIM APIs $PrivilegedRoles = $Tracker.TryCommand("Get-PrivilegedRole", @{"TenantHasPremiumLicense"=$true; "M365Environment"=$M365Environment}) } else { $PrivilegedRoles = $Tracker.TryCommand("Get-PrivilegedRole", @{"TenantHasPremiumLicense"=$false; "M365Environment"=$M365Environment}) } $PrivilegedRoles = ConvertTo-Json -Depth 10 @($PrivilegedRoles) # Depth required to get policy rule object details } else { Write-Warning "Omitting calls to Get-PrivilegedRole and Get-PrivilegedUser." $PrivilegedUsers = ConvertTo-Json @() $PrivilegedRoles = ConvertTo-Json @() $Tracker.AddUnSuccessfulCommand("Get-PrivilegedRole") $Tracker.AddUnSuccessfulCommand("Get-PrivilegedUser") } $ServicePlans = ConvertTo-Json -Depth 3 @($ServicePlans) $UserCount = $Tracker.TryCommand("Get-MgBetaUserCount", @{"ConsistencyLevel"='eventual'}) # Ensure we successfully got a count of users if(-Not $UserCount -is [int]) { $UserCount = "NaN" } # Provides data for policies such as user consent and guest user access $AuthZPolicies = ConvertTo-Json @($Tracker.TryCommand("Get-MgBetaPolicyAuthorizationPolicy")) # Provides data for admin consent workflow $DirectorySettings = ConvertTo-Json -Depth 10 @($Tracker.TryCommand("Get-MgBetaDirectorySetting")) ##### This block supports policies that need data on the tenant's authentication methods $AuthenticationMethodPolicyRootObject = $Tracker.TryCommand("Get-MgBetaPolicyAuthenticationMethodPolicy") $AuthenticationMethodFeatureSettings = @($AuthenticationMethodPolicyRootObject.AuthenticationMethodConfigurations | Where-Object { $_.Id}) # Exclude the AuthenticationMethodConfigurations so we do not duplicate it in the JSON $AuthenticationMethodPolicy = $AuthenticationMethodPolicyRootObject | ForEach-Object { $_ | Select-Object * -ExcludeProperty AuthenticationMethodConfigurations } $AuthenticationMethodObjects = @{ authentication_method_policy = $AuthenticationMethodPolicy authentication_method_feature_settings = $AuthenticationMethodFeatureSettings } $AuthenticationMethod = ConvertTo-Json -Depth 10 @($AuthenticationMethodObjects) ##### End block # Provides data on the password expiration policy $DomainSettings = ConvertTo-Json @($Tracker.TryCommand("Get-MgBetaDomain")) $SuccessfulCommands = ConvertTo-Json @($Tracker.GetSuccessfulCommands()) $UnSuccessfulCommands = ConvertTo-Json @($Tracker.GetUnSuccessfulCommands()) # Note the spacing and the last comma in the json is important $json = @" "conditional_access_policies": $AllPolicies, "cap_table_data": $CapTableData, "authorization_policies": $AuthZPolicies, "privileged_users": $PrivilegedUsers, "privileged_roles": $PrivilegedRoles, "service_plans": $ServicePlans, "directory_settings": $DirectorySettings, "authentication_method": $AuthenticationMethod, "domain_settings": $DomainSettings, "license_information": $LicenseInfo, "total_user_count": $UserCount, "aad_successful_commands": $SuccessfulCommands, "aad_unsuccessful_commands": $UnSuccessfulCommands, "@ $json } function Get-AADTenantDetail { <# .Description Gets the tenant details using the Microsoft Graph PowerShell Module .Functionality Internal #> try { $OrgInfo = Get-MgBetaOrganization -ErrorAction "Stop" $InitialDomain = $OrgInfo.VerifiedDomains | Where-Object {$_.isInitial} if (-not $InitialDomain) { $InitialDomain = "AAD: Domain Unretrievable" } $AADTenantInfo = @{ "DisplayName" = $OrgInfo.DisplayName; "DomainName" = $InitialDomain.Name; "TenantId" = $OrgInfo.Id; "AADAdditionalData" = $OrgInfo; } $AADTenantInfo = ConvertTo-Json @($AADTenantInfo) -Depth 4 $AADTenantInfo } catch { Write-Warning "Error retrieving Tenant details using Get-AADTenantDetail $($_)" $AADTenantInfo = @{ "DisplayName" = "Error retrieving Display name"; "DomainName" = "Error retrieving Domain name"; "TenantId" = "Error retrieving Tenant ID"; "AADAdditionalData" = "Error retrieving additional data"; } $AADTenantInfo = ConvertTo-Json @($AADTenantInfo) -Depth 4 $AADTenantInfo } } function Get-PrivilegedUser { <# .Description Returns a hashtable of privileged users and their respective roles .Functionality Internal #> param ( [ValidateNotNullOrEmpty()] [bool] $TenantHasPremiumLicense, [ValidateNotNullOrEmpty()] [string] $M365Environment ) try { # A hashtable of privileged users $PrivilegedUsers = @{} $PrivilegedRoles = [ScubaConfig]::ScubaDefault('DefaultPrivilegedRoles') # Get a list of the Id values for the privileged roles in the list above. # The Id value is passed to other cmdlets to construct a list of users assigned to privileged roles. $AADRoles = Get-MgBetaDirectoryRole -All -ErrorAction Stop | Where-Object { $_.DisplayName -in $PrivilegedRoles } # Construct a list of privileged users based on the Active role assignments foreach ($Role in $AADRoles) { # Get a list of all the users and groups Actively assigned to this role $UsersAssignedRole = Get-MgBetaDirectoryRoleMember -All -ErrorAction Stop -DirectoryRoleId $Role.Id foreach ($User in $UsersAssignedRole) { $Objecttype = $User.AdditionalProperties."@odata.type" -replace "#microsoft.graph." if ($Objecttype -eq "user") { LoadObjectDataIntoPrivilegedUserHashtable -RoleName $Role.DisplayName -PrivilegedUsers $PrivilegedUsers -ObjectId $User.Id -TenantHasPremiumLicense $TenantHasPremiumLicense -M365Environment $M365Environment -Objecttype "user" } elseif ($Objecttype -eq "group") { # In this context $User.Id is a group identifier $GroupId = $User.Id # Process all of the group members that are transitively assigned to the current role as Active via group membership LoadObjectDataIntoPrivilegedUserHashtable -RoleName $Role.DisplayName -PrivilegedUsers $PrivilegedUsers -ObjectId $GroupId -TenantHasPremiumLicense $TenantHasPremiumLicense -M365Environment $M365Environment -Objecttype "group" } } } # Process the Eligible role assignments if the premium license for PIM is there if ($TenantHasPremiumLicense) { # Get a list of all the users and groups that have Eligible assignments $graphArgs = @{ "commandlet" = "Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleInstance" "M365Environment" = $M365Environment } $AllPIMRoleAssignments = Invoke-GraphDirectly @graphArgs # Add to the list of privileged users based on Eligible assignments foreach ($Role in $AADRoles) { $PrivRoleId = $Role.RoleTemplateId # Get a list of all the users and groups Eligible assigned to this role $PIMRoleAssignments = $AllPIMRoleAssignments | Where-Object { $_.RoleDefinitionId -eq $PrivRoleId } foreach ($PIMRoleAssignment in $PIMRoleAssignments) { $UserObjectId = $PIMRoleAssignment.PrincipalId LoadObjectDataIntoPrivilegedUserHashtable -RoleName $Role.DisplayName -PrivilegedUsers $PrivilegedUsers -ObjectId $UserObjectId -TenantHasPremiumLicense $TenantHasPremiumLicense -M365Environment $M365Environment } } } } catch { Write-Warning "An error occurred in Get-PrivilegedUser: $($_.Exception.Message)" Write-Warning "Stack trace: $($_.ScriptStackTrace)" throw $_ } $PrivilegedUsers } function LoadObjectDataIntoPrivilegedUserHashtable { <# .Description Takes an object Id (either a user or group) and loads metadata about the object in the provided privileged user hashtable. If the object is a group, this function will iterate the group members and load metadata about each member. .Functionality Internal #> param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$RoleName, [Parameter(Mandatory=$true)] [hashtable]$PrivilegedUsers, # The Entra Id unique identifiter for an object (either a user or a group) in the directory. # Metadata about this object will be loaded into the PrivilegedUsers hashtable which is passed as a parameter. [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ObjectId, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [bool]$TenantHasPremiumLicense, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$M365Environment, # This describes the type of Entra Id object that the parameter ObjectId is referencing. # Valid values are "user", "group". If this is not passed, the function will call Graph to dynamically determine the object type. [Parameter()] [string]$Objecttype = "", [Parameter()] [int]$Recursioncount = 0 ) # Write-Warning "Recursion level: $recursioncount" # We support group nesting up to 2 levels deep (stops after processing levels 0 and 1). # Safeguard: Also protects against infinite loops if there is a circular group assignment in PIM. if ($recursioncount -ge 2) { return } # If the object type was not supplied we need to determine whether it is a user or a group. if ($Objecttype -eq "") { try { $DirectoryObject = Get-MgBetaDirectoryObject -ErrorAction Stop -DirectoryObjectId $ObjectId } catch { # If the object was probably recently deleted from the directory we ignore it. Otherwise an unhandled 404 causes the tool to crash. if ($_.Exception.Message -match "Request_ResourceNotFound") { Write-Warning "Processing privileged users. Resource $ObjectId may have been recently deleted from the directory because it was not found." return # Exit the function to ignore this resource and keep the flow going. } # If it is a different error, rethrow the error to let the calling function handle it. else { throw $_ } } # Extract what type of object this is. $Objecttype = $DirectoryObject.AdditionalProperties."@odata.type" -replace "#microsoft.graph." } if ($Objecttype -eq "user") { # If the user's data has not been fetched from graph, go get it and add it to the hashtable if (-Not $PrivilegedUsers.ContainsKey($ObjectId)) { $AADUser = Get-MgBetaUser -ErrorAction Stop -UserId $ObjectId $PrivilegedUsers[$ObjectId] = @{"DisplayName"=$AADUser.DisplayName; "OnPremisesImmutableId"=$AADUser.OnPremisesImmutableId; "roles"=@()} # Write-Warning "Processing role: $($RoleName) User: $($AADUser.DisplayName)" } # If the current role has not already been added to the user's roles array then add the role if ($PrivilegedUsers[$ObjectId].roles -notcontains $RoleName) { $PrivilegedUsers[$ObjectId].roles += $RoleName } } elseif ($Objecttype -eq "group") { # In this context $ObjectId is a group identifier so we need to iterate the group members $GroupId = $ObjectId # Get all of the group members that are transitively assigned to the current role via group membership $GroupMembers = Get-MgBetaGroupMember -All -ErrorAction Stop -GroupId $GroupId # Write-Warning "Processing role: $($RoleName) Group: $($GroupId)" foreach ($GroupMember in $GroupMembers) { $Membertype = $GroupMember.AdditionalProperties."@odata.type" -replace "#microsoft.graph." if ($Membertype -eq "user") { # If the user's data has not been fetched from graph, go get it and add it to the hashtable if (-Not $PrivilegedUsers.ContainsKey($GroupMember.Id)) { $AADUser = Get-MgBetaUser -ErrorAction Stop -UserId $GroupMember.Id $PrivilegedUsers[$GroupMember.Id] = @{"DisplayName"=$AADUser.DisplayName; "OnPremisesImmutableId"=$AADUser.OnPremisesImmutableId; "roles"=@()} } # If the current role has not already been added to the user's roles array then add the role if ($PrivilegedUsers[$GroupMember.Id].roles -notcontains $RoleName) { $PrivilegedUsers[$GroupMember.Id].roles += $RoleName } } } # Since this is a group, we need to also process assignments in PIM in case it is in PIM for Groups # If the premium license for PIM is there, process the users that are "member" of the PIM group as Eligible if ($TenantHasPremiumLicense) { # Get the users that are assigned to the PIM group as Eligible members $graphArgs = @{ "commandlet" = "Get-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleInstance" "queryParams" = @{'$filter' = "groupId eq '$GroupId'"} "M365Environment" = $M365Environment } $PIMGroupMembers = Invoke-GraphDirectly @graphArgs foreach ($GroupMember in $PIMGroupMembers) { # Write-Warning "Processing role: $($RoleName) PIM group Eligible member: $($GroupMember.PrincipalId)" # If the user is not a member of the PIM group (i.e. they are an owner) then skip them if ($GroupMember.AccessId -ne "member") { continue } $PIMEligibleUserId = $GroupMember.PrincipalId # Recursively call this function to process the group member that was found $LoopIterationRecursioncount = $Recursioncount + 1 LoadObjectDataIntoPrivilegedUserHashtable -RoleName $RoleName -PrivilegedUsers $PrivilegedUsers -ObjectId $PIMEligibleUserId -TenantHasPremiumLicense $TenantHasPremiumLicense -M365Environment $M365Environment -Recursioncount $LoopIterationRecursioncount } } } } function AddRuleSource{ <# .NOTES Internal helper function to add a source to policy rule for reporting purposes. Source should be either PIM Group Name or Role Name #> param( [ValidateNotNullOrEmpty()] [string] $Source, [ValidateNotNullOrEmpty()] [string] $SourceType = "Directory Role", [ValidateNotNullOrEmpty()] [array] $Rules ) foreach ($Rule in $Rules){ $Rule | Add-Member -Name "RuleSource" -Value $Source -MemberType NoteProperty $Rule | Add-Member -Name "RuleSourceType" -Value $SourceType -MemberType NoteProperty } } # This cache keeps track of PIM groups that we've already processed class GroupTypeCache{ static [hashtable]$CheckedGroups = @{} } function GetConfigurationsForPimGroups{ param ( [ValidateNotNullOrEmpty()] [array] $PrivilegedRoleArray, [ValidateNotNullOrEmpty()] [array] $AllRoleAssignments, [ValidateNotNullOrEmpty()] [string] $M365Environment ) # Get a list of the groups that are enrolled in PIM - we want to ignore the others $graphArgs = @{ "commandlet" = "Get-MgBetaPrivilegedAccessResource" "queryParams" = @{'$PrivilegedAccessId' = "aadGroups"} "M365Environment" = $M365Environment } $PIMGroups = Invoke-GraphDirectly @graphArgs foreach ($RoleAssignment in $AllRoleAssignments){ # Check if the assignment in current loop iteration is assigned to a privileged role $Role = $PrivilegedRoleArray | Where-Object RoleTemplateId -EQ $($RoleAssignment.RoleDefinitionId) # If this is a privileged role if ($Role){ # Store the Id of the object assigned to the role (could be user,group,service principal) $PrincipalId = $RoleAssignment.PrincipalId # If the current object is not a PIM group we skip it $FoundPIMGroup = $PIMGroups | Where-Object { $_.Id -eq $PrincipalId } if ($null -eq $FoundPIMGroup) { continue } # If we haven't processed the current group before, add it to the cache and proceed If ($null -eq [GroupTypeCache]::CheckedGroups[$PrincipalId]){ [GroupTypeCache]::CheckedGroups.Add($PrincipalId, $true) } # If we have processed it before, then skip it to avoid unnecessary cycles else { continue } # Get all the configuration rules for the current PIM group - get member not owner configs $PolicyAssignment = Get-MgBetaPolicyRoleManagementPolicyAssignment -All -ErrorAction Stop -Filter "scopeId eq '$PrincipalId' and scopeType eq 'Group' and roleDefinitionId eq 'member'" | Select-Object -Property PolicyId # Add each configuration rule to the array. There are usually about 17 configurations for a group. # Get the detailed configuration settings $MemberPolicyRules = Get-MgBetaPolicyRoleManagementPolicyRule -All -ErrorAction Stop -UnifiedRoleManagementPolicyId $PolicyAssignment.PolicyId # Filter for the PIM group so we can grab its name $PIMGroup = $PIMGroups | Where-Object {$_.Id -eq $PrincipalId} # $SourceGroup = Get-MgBetaGroup -Filter "id eq '$PrincipalId' " | Select-Object -Property DisplayName AddRuleSource -Source $PIMGroup.DisplayName -SourceType "PIM Group" -Rules $MemberPolicyRules $RoleRules = $Role.psobject.Properties | Where-Object {$_.Name -eq 'Rules'} if ($RoleRules){ # Appending rules $Role.Rules += $MemberPolicyRules } else { # Adding rules node if it is not already present $Role | Add-Member -Name "Rules" -Value $MemberPolicyRules -MemberType NoteProperty } } } } function GetConfigurationsForRoles{ param ( [ValidateNotNullOrEmpty()] [array] $PrivilegedRoleArray, [ValidateNotNullOrEmpty()] [array] $AllRoleAssignments ) # Get all the configuration settings (aka rules) for all the roles in the tenant $RolePolicyAssignments = Get-MgBetaPolicyRoleManagementPolicyAssignment -All -ErrorAction Stop -Filter "scopeId eq '/' and scopeType eq 'DirectoryRole'" foreach ($Role in $PrivilegedRoleArray) { $RolePolicies = @() $RoleTemplateId = $Role.RoleTemplateId # Get a list of the configuration rules assigned to this role $PolicyAssignment = $RolePolicyAssignments | Where-Object -Property RoleDefinitionId -eq -Value $RoleTemplateId # Get the detailed configuration settings $RolePolicies = Get-MgBetaPolicyRoleManagementPolicyRule -All -ErrorAction Stop -UnifiedRoleManagementPolicyId $PolicyAssignment.PolicyId # Get a list of the users / groups assigned to this role $RoleAssignments = @($AllRoleAssignments | Where-Object { $_.RoleDefinitionId -eq $RoleTemplateId }) # Store the data that we retrieved in the Role object which is part of the privileged role array $Role | Add-Member -Name "Assignments" -Value $RoleAssignments -MemberType NoteProperty $RoleRules = $Role.psobject.Properties | Where-Object {$_.Name -eq 'Rules'} AddRuleSource -Source $Role.DisplayName -SourceType "Directory Role" -Rules $RolePolicies if ($RoleRules){ $Role.Rules += $RolePolicies } else { $Role | Add-Member -Name "Rules" -Value $RolePolicies -MemberType NoteProperty } } } function Get-PrivilegedRole { <# .Description Returns an array of the highly privileged roles along with the users actively assigned to the role and the security configurations applied to the role .Functionality Internal #> param ( [ValidateNotNullOrEmpty()] [bool] $TenantHasPremiumLicense, [ValidateNotNullOrEmpty()] [string] $M365Environment ) try { $PrivilegedRoles = [ScubaConfig]::ScubaDefault('DefaultPrivilegedRoles') # Get a list of the RoleTemplateId values for the privileged roles in the list above. # The RoleTemplateId value is passed to other cmdlets to retrieve role/group security configuration rules and user/group assignments. $PrivilegedRoleArray = Get-MgBetaDirectoryRoleTemplate -All -ErrorAction Stop | Where-Object { $_.DisplayName -in $PrivilegedRoles } | Select-Object "DisplayName", @{Name='RoleTemplateId'; Expression={$_.Id}} # If the tenant has the premium license then you can access the PIM service to get the role configuration policies and the active role assigments if ($TenantHasPremiumLicense) { # Clear the cache of already processed PIM groups because this is a static variable [GroupTypeCache]::CheckedGroups.Clear() # Get ALL the roles and users actively assigned to them $graphArgs = @{ "commandlet" = "Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleInstance" "M365Environment" = $M365Environment } $AllRoleAssignments = Invoke-GraphDirectly @graphArgs # Each of the helper functions below add configuration settings (aka rules) to the role array. # Get the PIM configurations for the roles GetConfigurationsForRoles -PrivilegedRoleArray $PrivilegedRoleArray -AllRoleAssignments $AllRoleAssignments # Get the PIM configurations for the groups GetConfigurationsForPimGroups -PrivilegedRoleArray $PrivilegedRoleArray -AllRoleAssignments $AllRoleAssignments -M365Environment $M365Environment } } catch { Write-Warning "An error occurred in Get-PrivilegedRole: $($_.Exception.Message)" Write-Warning "Stack trace: $($_.ScriptStackTrace)" throw $_ } # Return the array $PrivilegedRoleArray } # SIG # Begin signature block # MIIuugYJKoZIhvcNAQcCoIIuqzCCLqcCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDZsK0Raef2lP1r # 4ygaJaqG4eQMbMOpyfAs9ygc1jgT6KCCE6MwggWQMIIDeKADAgECAhAFmxtXno4h # MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV # BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z # ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 # IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB # AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z # G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ # anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s # Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL # 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb # BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3 # JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c # AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx # YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0 # viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL # T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud # EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf # Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk # aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS # PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK # 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB # cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp # 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg # dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri # RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7 # 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5 # nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3 # i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H # EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G # CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 # IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla # MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE # AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz # ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C # 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce # 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da # E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T # SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA # FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh # D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM # 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z # 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05 # huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY # mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP # /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T # AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD # VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG # A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV # HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU # cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN # BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry # sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL # IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf # Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh # OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh # dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV # 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j # wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH # Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC # XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l # /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW # eE4wggdXMIIFP6ADAgECAhANkQ8dPvvR0q3Ytt4H0T3aMA0GCSqGSIb3DQEBCwUA # MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE # AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz # ODQgMjAyMSBDQTEwHhcNMjQwMTMwMDAwMDAwWhcNMjUwMTI5MjM1OTU5WjBfMQsw # CQYDVQQGEwJVUzEdMBsGA1UECBMURGlzdHJpY3Qgb2YgQ29sdW1iaWExEzARBgNV # BAcTCldhc2hpbmd0b24xDTALBgNVBAoTBENJU0ExDTALBgNVBAMTBENJU0EwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCT1y7uJCQax8JfiDEYgpiU9URj # EXCTRqtZbDALM9rPUudiuM3mj6A1SUSAAWYv6DTsvGPvxyMI2Idg0mQunl4Ms9DJ # yVwe5k4+Anj/73Nx1AbOPYP8xRZcD10FkctKGhV0PzvrDcwU15hsQWtiepFgg+bX # fHkGMeu426oc69f43vKE43DiqKTf0/UBX/qgpj3JZvJ3zc1kilBOv4sBCksfCjbW # tLZD0tqAgBsNPo3Oy5mQG31E1eZdTNvrdTnEXacSwb3k615z7mHy7nqBUkOruZ9E # tnvC2qla+uL3ks91O/e/LnKzH9Lj1JmEBf6jwPN/MYR9Dymni4Mi3AQ8mpQMyFmi # XcSHymibSNbtTMavpdBWjFfrcvPETX7krROUOoLzMQmNgHArceSh55tgvDRdSU5c # WK3BTvK3l3mgCdgjre7XGYxV3W8apyxk5+RKfHdbv9cpRwpSuDnI8sHeqmB3fnfo # Cr1PPu4WhKegt20CobhDVybiBdhDVqUdR53ful4N/coQOEHDrIExB5nJf9Pvdrza # DyIGKAMIXD79ba5/rQEo+2cA66oJkPlvB5hEGI/jtDcYwDBgalbwB7Kc8zAAhl6+ # JvHfYpXOkppSfEQbaRXZI+LGXWQAFa5pJDfDEAyZSXprStgw594sWUOysp+UOxFe # kSA4mBr0o1jVpdaulwIDAQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8R # hvv+YXsIiGX0TkIwHQYDVR0OBBYEFAmyTB5bcWyA+8+rq540jPRLJ1nYMD4GA1Ud # IAQ3MDUwMwYGZ4EMAQQBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNl # cnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMw # gbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5j # cmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0 # ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3JsMIGUBggrBgEF # BQcBAQSBhzCBhDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t # MFwGCCsGAQUFBzAChlBodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNl # cnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJ # BgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQAh2Jnt9IPoBvOQlYQUlCP9iJ5y # XAvEWe1camOwedqMZsHEPpT2yd6+fMzPZmV3/bYJgaN2OrDS1snf62S7yc+AulVw # PAXSp1lSAiFEbZ6PFEdEBIag9B65Mp/cvRtJsIWQIc//jWqFMHpkU6r3MW9YARRu # vaIf5/0qlM4VEwn3lTf+jJdxhhyoOFTWWd3BrlMPcT06z6F6hFfyycQkZ3Y9wEJ3 # uOU9bCNLZL1HCjlKT+oI0WsgeRdbe2sYrnvv9NmDY9oEi8PEq+DGjiTgLbY5OcAX # uUogPPw6gbcuNn8Hq6FFKPIQxaksB8dF8Gw4m2lQoUWESPRF8Zaq9lmZN3+QzA79 # yskfJtAFqz3gUP5wJBdNfi/u1sGbLI0QnJQkIKfFuz7DfDPldw0gIl05BIYwZBmj # TpFRu1/+gIlP1Ul4L/wt9Lxk6pglObLsdxHP2UQrG30JaUN0gv3xZMBBByHGVVTe # cyU4qwJ0ulMdv/kjHwh+m58uOF8gHXLfyBmOjYpohN3+l0rS0qdArZMNSmLTA7N8 # n3V3AZLKB//1yhPt++gR4pCFdXmgwYDDLRxjlV0cMsG1UeSQUdI0aieh/grg5TQO # CergVXS5h3sz5U0ZQPWND41LJhA0gF2OGZNHdUc9+0dwTsfxAERrjaTdeZp0/rdZ # 9iGBoiRsS4U86S8xkDGCGm0wghppAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNV # BAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0 # IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQDZEPHT770dKt # 2LbeB9E92jANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgACh # AoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAM # BgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCCPHKtHzNnANuy1/8WaQEodE59Q # UhYzpLwsVOn56BJW+jANBgkqhkiG9w0BAQEFAASCAgCTvJSh6cFfjmNcI+LIBzR3 # vfgb4zMt0eYkVHuqshzzkRLADeDy8NwF8VAonk44CjzLWdnMahzQgly8otfLESJg # umRVV96CmaoiMBtawbtu2jbmAV4BRVhuxWtYgrcco1x5WKe98oMqothF2+vscmzf # uAKYOSjBq9v97GsMUwGKkRV7I82APUHM+i1dHQ/IUepnOdgiYZ4qVJ2jABz0LzA7 # qX3pOj7v/XNuBQuIBmLXQJwRLOIfcVFW9TGn1zDOTncvZ6T72T2V/qA6yY2Q1UrU # XCYLNYaxhqato9DdV097WDYopJnqSODX87pMUPsTXe8Piy8L+T7tjlYXgkIjw7aV # ADUOhLpM+Ooy07eNZeM0nph4DKuV8te6FwY8/dgEW7ZaxDoQEqCEpOGL3SL8WxhZ # G/469loLJWAj3A2RiQryyRLritMbzThzXy6uCHAE9969KiYuq/8DvaTmZEj+RTHZ # D2KyyS1dWnNFdDBMcdDs2/bgHgBRayAwi8ApP8Yfs5tP+ZRJ7B4eoy9M26TRAlkE # jDpZ0jZjl2GN47ZyOztlS7//E3xk86bm9aeToigumRUuUhhZualghx8vGAjK/1aN # YC0YXrLVnkMNRzyVXIlCKW/6GMvdUL7bWnl+PBy4JLlgdpoCh+rKgABAiGqQrUO1 # uyupOQQINYNvvNBsTQ31x6GCFzowghc2BgorBgEEAYI3AwMBMYIXJjCCFyIGCSqG # SIb3DQEHAqCCFxMwghcPAgEDMQ8wDQYJYIZIAWUDBAIBBQAweAYLKoZIhvcNAQkQ # AQSgaQRnMGUCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCDxxSaGBEAR # ifo6tIjdIAIyRT/OivXtDSmJi8pu8oaN4gIRAL7VWmuO4OkYXz4jNLvR+i8YDzIw # MjQxMDExMTUxMTEwWqCCEwMwgga8MIIEpKADAgECAhALrma8Wrp/lYfG+ekE4zME # MA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy # dCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNI # QTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjQwOTI2MDAwMDAwWhcNMzUxMTI1MjM1 # OTU5WjBCMQswCQYDVQQGEwJVUzERMA8GA1UEChMIRGlnaUNlcnQxIDAeBgNVBAMT # F0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDI0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A # MIICCgKCAgEAvmpzn/aVIauWMLpbbeZZo7Xo/ZEfGMSIO2qZ46XB/QowIEMSvgjE # dEZ3v4vrrTHleW1JWGErrjOL0J4L0HqVR1czSzvUQ5xF7z4IQmn7dHY7yijvoQ7u # jm0u6yXF2v1CrzZopykD07/9fpAT4BxpT9vJoJqAsP8YuhRvflJ9YeHjes4fduks # THulntq9WelRWY++TFPxzZrbILRYynyEy7rS1lHQKFpXvo2GePfsMRhNf1F41nyE # g5h7iOXv+vjX0K8RhUisfqw3TTLHj1uhS66YX2LZPxS4oaf33rp9HlfqSBePejlY # eEdU740GKQM7SaVSH3TbBL8R6HwX9QVpGnXPlKdE4fBIn5BBFnV+KwPxRNUNK6lY # k2y1WSKour4hJN0SMkoaNV8hyyADiX1xuTxKaXN12HgR+8WulU2d6zhzXomJ2Ple # I9V2yfmfXSPGYanGgxzqI+ShoOGLomMd3mJt92nm7Mheng/TBeSA2z4I78JpwGpT # RHiT7yHqBiV2ngUIyCtd0pZ8zg3S7bk4QC4RrcnKJ3FbjyPAGogmoiZ33c1HG93V # p6lJ415ERcC7bFQMRbxqrMVANiav1k425zYyFMyLNyE1QulQSgDpW9rtvVcIH7Wv # G9sqYup9j8z9J1XqbBZPJ5XLln8mS8wWmdDLnBHXgYly/p1DhoQo5fkCAwEAAaOC # AYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM # MAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATAf # BgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4EFgQUn1csA3cO # KBWQZqVjXu5Pkh92oFswWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFt # cGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUHMAGGGGh0dHA6 # Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDovL2NhY2VydHMu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVT # dGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAPa0eH3aZW+M4hBJH2UOR # 9hHbm04IHdEoT8/T3HuBSyZeq3jSi5GXeWP7xCKhVireKCnCs+8GZl2uVYFvQe+p # PTScVJeCZSsMo1JCoZN2mMew/L4tpqVNbSpWO9QGFwfMEy60HofN6V51sMLMXNTL # fhVqs+e8haupWiArSozyAmGH/6oMQAh078qRh6wvJNU6gnh5OruCP1QUAvVSu4kq # VOcJVozZR5RRb/zPd++PGE3qF1P3xWvYViUJLsxtvge/mzA75oBfFZSbdakHJe2B # VDGIGVNVjOp8sNt70+kEoMF+T6tptMUNlehSR7vM+C13v9+9ZOUKzfRUAYSyyEmY # tsnpltD/GWX8eM70ls1V6QG/ZOB6b6Yum1HvIiulqJ1Elesj5TMHq8CWT/xrW7tw # ipXTJ5/i5pkU5E16RSBAdOp12aw8IQhhA/vEbFkEiF2abhuFixUDobZaA0VhqAsM # HOmaT3XThZDNi5U2zHKhUs5uHHdG6BoQau75KiNbh0c+hatSF+02kULkftARjsyE # pHKsF7u5zKRbt5oK5YGwFvgc4pEVUNytmB3BpIiowOIIuDgP5M9WArHYSAR16gc0 # dP2XdkMEP5eBsX7bf/MGN4K3HP50v/01ZHo/Z5lGLvNwQ7XHBx1yomzLP8lx4Q1z # ZKDyHcp4VQJLu2kWTsKsOqQwggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5b # MA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5 # NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkG # A1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3Rh # bXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPB # PXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/ # nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLc # Z47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mf # XazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3N # Ng1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yem # j052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g # 3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD # 4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDS # LFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwM # O1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU # 7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/ # BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0j # BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud # JQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0 # cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E # PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz # dGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEw # DQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPO # vxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQ # TGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWae # LJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPBy # oyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfB # wWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8l # Y5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/ # O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbb # bxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3 # OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBl # dkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt # 1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkqhkiG9w0BAQwF # ADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL # ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElE # IFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5WjBiMQswCQYD # VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGln # aWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKn # JS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/W # BTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHi # LQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhm # V1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHE # tWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 # MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mX # aXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZ # xd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfh # vbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvl # EFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn1 # 5GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV # HQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAUReuir/SSy4Ix # LVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEBBG0wazAkBggr # BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdo # dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290 # Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAowCDAGBgRVHSAA # MA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/Vwe9mqyhhyzs # hV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLeJLxSA8hO0Cre # +i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE1Od/6Fmo8L8v # C6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9HdaXFSMb++hUD38 # dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbObyMt9H5xaiNr # Iv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMYIDdjCCA3ICAQEw # dzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNV # BAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1w # aW5nIENBAhALrma8Wrp/lYfG+ekE4zMEMA0GCWCGSAFlAwQCAQUAoIHRMBoGCSqG # SIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjQxMDExMTUx # MTEwWjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBTb04XuYtvSPnvk9nFIUIck1YZb # RTAvBgkqhkiG9w0BCQQxIgQghh2kohzNdEbmUgkP27dPNH1zB5QDCEvTHQAuYzN6 # TdMwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgdnafqPJjLx9DCzojMK7WVnX+13Pb # BdZluQWTmEOPmtswDQYJKoZIhvcNAQEBBQAEggIAvb7gx5a3FrEf2bz4wZwPtw5J # uO1VTpjvUOWQSwnAumKs5NSw+RMoEfrcrnMyEdZuZqlHCXR012u7EtpVRAc+jYVN # 24rIqgu74eE/Vq/VI9m4t11ONalR0FCljIpdh/gy1yBC2pboX0Dn6IyWuwLok4ns # oV/ioBYRJTgwtIADml46fpuqq/cLOK7QQVYmzmOsA/sN4NYGz/BZ4xm2kt6qDqua # JHuEZ2OcUFRcwEqJyU49JVimoJq++3y0Vi0OcCLMt5DvD1NFmEXV6znekh2xDuMt # maVNoHYKPLwWkH2u/L/Nck7F3vRrjqqOuSDGJ/nKXLI13TSdfHm9pPC0VtdxcSlS # 57DRmwT8Wa7pg8yIT3mG1uTd9dvhZ+eHkqMIChpi//n0YE3/hHK6aO+nHPq7tuwA # l81YccvuRiZt7KqGCIjAnoRehPd4ofTI8RD6NAJSCZv0gkRgGR0NVFZGh5k2vvYU # twIsguV0eYnDQs8JPYdLh2ibrTEFttWrvRxcfLm8A8zyUet1BUC+qi5bgwz2ZVSG # xbH/bz5cgK0qutbZso7obRxp+SbrVktVe45HbdNWlQh9JUEKRDytn8u/rVwEswMD # sqkJHw5q/aXi/LRNzIHzcGoo+XMqr2+nrg8r+S4rJz8ih0OgcTrZhx72iayFbIa3 # bmggPtqBswp0Cw+99rw= # SIG # End signature block |