Public/ServiceEM/New-EntraOpsSubscriptionLandingZone.ps1
|
<# .SYNOPSIS Creates an EAM authorization structure for an Azure resource group or subscription. .DESCRIPTION Provisions EAM groups, an Entitlement Management catalog, access packages, PIM policies and Azure role assignments for one scope. -DeploymentScope selects it: ResourceGroup (default) — scope "Rg-<Prefix>": catalog Catalog-Rg-<Prefix>, resource group RG-<Prefix> with all Azure role assignments on the resource group. Subscription — scope "Sub-<Prefix>": catalog Catalog-Sub-<Prefix>, no resource group; the same Azure role assignments are made on the subscription (-SubscriptionId). To share governance groups between landing zones, pass their IDs as -ControlPlaneDelegationGroupId / -ManagementPlaneDelegationGroupId / -AdministratorGroupId or use -GovernanceModel Centralized. Groups (PerService): SG-<Scope>-CatalogPlane-Members, SG-<Scope>-WorkloadPlane-Users, SG-<Scope>-WorkloadPlane-Admins, SG-<Scope>-ControlPlane-Admins and SG-<Scope>-ManagementPlane-Admins, and with -CreateM365Group the Microsoft 365 group <Scope> Members. Delegation and governance model behaviour: - When GovernanceModel = "PerService" (default), per-service groups are created for ControlPlane-Admins and ManagementPlane-Admins. - When GovernanceModel = "Centralized", ControlPlane-Admins, ManagementPlane-Admins and CatalogPlane-Members are resolved to tenant-wide shared groups via Resolve-EntraOpsServiceEMDelegationGroup. - Delegation group IDs are read from EntraOpsConfig.ServiceEM when not passed as parameters. .PARAMETER DeploymentScope "ResourceGroup" (default) or "Subscription". .PARAMETER ServiceMembers UPN(s) of users to add as initial WorkloadPlane-Users. Defaults to the signed-in identity. .PARAMETER WorkloadPlaneAdmin UPN of the workload plane admin, assigned to the admin access package (ManagementPlane-Admins or WorkloadPlane-Admins) and to CatalogPlane-Members. Defaults to the signed-in identity only when -GroupOwnership is Eligible or Permanent. .PARAMETER GroupOwnership Opt-in ownership of the WorkloadPlane groups (WorkloadPlane-Admins, WorkloadPlane-Users) for the workload plane admin: "None" (default), "Eligible" (PIM for Groups eligible owner) or "Permanent" (owner set when the groups are created). Owners can add members directly, bypassing access package approvals and access reviews, so a warning is shown. ControlPlane, ManagementPlane, CatalogPlane and tenant-wide delegation groups never get an owner. .PARAMETER ControlPlaneAdmins UPN(s) of the initial members of the per-service ControlPlane-Admins group (PerService model only; in the Centralized model the tenant-wide group is managed outside ServiceEM). Added permanently, or as PIM for Groups eligible members with -EnablePimForGroups. Without this parameter an Entra administrator adds the members in the portal. .PARAMETER CatalogPlaneMembers UPN(s) assigned to the CatalogPlane-Members access package, in addition to the WorkloadPlaneAdmin. CatalogPlane-Members (the administrator group) request WorkloadPlane-Admins and ManagementPlane-Admins; non-members can't request CatalogPlane-Members. Ignored with AdministratorGroupId. .PARAMETER AddWorkloadPlaneAdminToUsers Also assigns the workload plane admin to the WorkloadPlane-Users access package, in addition to the admin access package. Not set by default, so admin accounts don't get data-plane user access. Defaults to EntraOpsConfig.ServiceEM.AddWorkloadPlaneAdminToUsers; an explicitly passed value wins. .PARAMETER EnablePimForGroups Recommended. Manages ControlPlane-Admins and ManagementPlane-Admins with PIM for Groups: PIM activation policy and eligible instead of active membership through their access packages. Both groups hold permanent catalog roles that can't be PIM-protected otherwise. Requires Microsoft Entra ID Governance or Microsoft Entra Suite licenses (a warning is shown). Not set by default. .PARAMETER EnableWorkloadPlanePimForGroups Manages WorkloadPlane-Admins with PIM for Groups in the same way, e.g. for multi-activation scenarios. Requires Microsoft Entra ID Governance or Microsoft Entra Suite licenses (a warning is shown). Not set by default. .PARAMETER CreateM365Group Creates the Microsoft 365 group "<Scope>-<Prefix> Members". It is meant for the collaboration of the service team: a group mailbox and calendar for email and ChatOps notifications and, when SharePoint Online or Microsoft Teams is used, a SharePoint site or team as knowledge base. The group is added to every access package, so all users assigned to an access package become members. It gets no PIM for Groups eligibilities or other access. Not created by default. Defaults to EntraOpsConfig.ServiceEM.CreateM365Group; an explicitly passed value wins. .PARAMETER SkipAzureResourceGroup When set, no Azure resource group and no Azure role assignments are created. .PARAMETER AzureRegion Azure region for the resource group (e.g. "westeurope"). Required unless -SkipAzureResourceGroup is set or -DeploymentScope is "Subscription". .PARAMETER SubscriptionId Subscription in which the resource group (or, for -DeploymentScope Subscription, the subscription role assignments) is created. Required unless -SkipAzureResourceGroup is set. Validated before any object is created; the previous Azure context is restored afterwards. .PARAMETER DeploymentPrefix Prefix used in all group DisplayNames and catalog names. Defaults to "Default". Use the subscription or workload name (e.g. "Sub-Management", "Sub-Connectivity"). .PARAMETER SkipControlPlaneDelegation Skips creation of the per-service ControlPlane-Admins group and its Catalog Owner / Azure UAA PIM eligible assignments. Applied automatically when ControlPlaneDelegationGroupId is provided or when GovernanceModel is Centralized. .PARAMETER SkipCatalogOwnerAssignment Do not assign the Catalog Owner role to ControlPlane-Admins. Without this switch, ControlPlane-Admins get a permanent (not PIM-protected) Catalog Owner assignment and can modify the catalog, access packages and policies. Prefer an eligible Identity Governance Administrator assignment via PIM instead. .PARAMETER SkipManagementPlaneDelegation Skips creation of the per-service ManagementPlane-Admins group and its entitlement/Azure delegation. Applied automatically when ManagementPlaneDelegationGroupId is provided or when GovernanceModel is Centralized. .PARAMETER GovernanceModel Governance model for the landing zone deployment. Valid values: "Centralized", "PerService". Centralized: Uses tenant-wide delegation groups (ControlPlane-Admins and ManagementPlane-Admins) that are shared across all landing zones. Requires pre-existing groups or permissions to create them. PerService: Creates per-service admin groups for each landing zone. No pre-existing groups required. This is the default for simple deployments. Defaults to "PerService" unless overridden in EntraOpsConfig.json or via this parameter. If the tenant-wide delegation groups of the Centralized model can't be resolved, the cmdlet stops with an error before any object is created, unless -AllowPerServiceFallback is set. .PARAMETER AllowPerServiceFallback Opt-in for the Centralized governance model: when the tenant-wide ControlPlane or ManagementPlane delegation group can't be resolved or created, fall back to the PerService model and create per-service ControlPlane-Admins and ManagementPlane-Admins groups (with their privileged catalog and Azure roles) instead of stopping with an error. Not set by default. .PARAMETER ControlPlaneDelegationGroupId Object ID of an existing Entra group to use as ControlPlane-Admins instead of creating a per-service group, e.g. the ControlPlane-Admins of a governance landing zone shared by several workloads. Receives the Catalog Owner role and a PIM eligible User Access Administrator assignment. Falls back to EntraOpsConfig.ServiceEM.ControlPlaneDelegationGroupId. .PARAMETER ManagementPlaneDelegationGroupId Object ID of an existing Entra group to use as ManagementPlane-Admins instead of creating a per-service group. Receives the AP Assignment Manager catalog role, approver role in access package policies, and a PIM eligible Contributor role. Falls back to EntraOpsConfig.ServiceEM.ManagementPlaneDelegationGroupId. .PARAMETER AdministratorGroupId Object ID of an existing Entra group to use as CatalogPlane-Members instead of creating a per-service group. Controls who can request elevated access packages. Falls back to EntraOpsConfig.ServiceEM.AdministratorGroupId. Required with GovernanceModel "Centralized". .PARAMETER ControlPlaneGroupName Display name of the tenant-wide ControlPlane delegation group to look up or create when GovernanceModel is Centralized. Defaults to "PRG-Tenant-ControlPlane-IdentityOps". Overridden by EntraOpsConfig.ServiceEM.ControlPlaneGroupName. .PARAMETER ManagementPlaneGroupName Display name of the tenant-wide ManagementPlane delegation group to look up or create when GovernanceModel is Centralized. Defaults to "PRG-Tenant-ManagementPlane-PlatformOps". Overridden by EntraOpsConfig.ServiceEM.ManagementPlaneGroupName. .PARAMETER GroupPrefix Prefix of the security group display names (e.g. "SG" for SG-Rg-<Prefix>-WorkloadPlane-Users). A prefix ending with '-', '_' or '.' is used as is (e.g. "SG_" for SG_Rg-<Prefix>-WorkloadPlane-Users). Defaults to EntraOpsConfig.ServiceEM.GroupPrefix, otherwise "SG"; an explicitly passed value wins. .PARAMETER RoleAssignableWorkloadPlaneAdmins Creates WorkloadPlane-Admins as role-assignable group. ControlPlane, ManagementPlane and CatalogPlane groups are always role-assignable, WorkloadPlane-Users never. Only applies when a group is created. Defaults to EntraOpsConfig.ServiceEM.RoleAssignableWorkloadPlaneAdmins; an explicitly passed value wins. .PARAMETER logPrefix Text prepended to verbose messages. Defaults to the function name. .EXAMPLE New-EntraOpsSubscriptionLandingZone -DeploymentPrefix "Management" ` -AzureRegion "westeurope" -SubscriptionId "<subscription-id>" ` -WorkloadPlaneAdmin "admin@contoso.com" ` -ServiceMembers @("alice@contoso.com", "bob@contoso.com") Creates the Rg-Management scope with one catalog, the resource group RG-Management in West Europe and the Azure role assignments on it. Per-service admin groups are created (PerService governance model default). .EXAMPLE New-EntraOpsSubscriptionLandingZone -DeploymentPrefix "Connectivity" -DeploymentScope Subscription ` -SubscriptionId "<subscription-id>" -WorkloadPlaneAdmin "admin@contoso.com" Creates the Sub-Connectivity scope with one catalog and assigns the Azure roles on the subscription instead of a resource group. .EXAMPLE $governance = New-EntraOpsServiceBootstrap -ServiceName "Sub-Connectivity" -SkipAzureResourceGroup ` -EnablePimForGroups -ServiceRoles (@" accessLevel,name,groupType CatalogPlane,Members, ControlPlane,Admins, ManagementPlane,Admins, "@ | ConvertFrom-Csv) $groupId = { param($name) ($governance.Groups | Where-Object DisplayName -like "*-$name").Id } New-EntraOpsSubscriptionLandingZone -DeploymentPrefix "Connectivity" ` -AzureRegion "northeurope" -SubscriptionId "<subscription-id>" ` -ControlPlaneDelegationGroupId (& $groupId 'ControlPlane-Admins') ` -ManagementPlaneDelegationGroupId (& $groupId 'ManagementPlane-Admins') ` -AdministratorGroupId (& $groupId 'CatalogPlane-Members') Separates governance and workload groups: a governance scope Sub-Connectivity without Azure resources holds ControlPlane-Admins, ManagementPlane-Admins and CatalogPlane-Members; the workload landing zone Rg-Connectivity uses them as delegated groups for its catalog roles, approvals, access reviews and Azure roles on RG-Connectivity. Repeat the second call for further workloads. .EXAMPLE New-EntraOpsSubscriptionLandingZone -DeploymentPrefix "Dev" ` -SkipAzureResourceGroup Creates all Entra ID groups, the EM catalog and access packages for the Rg-Dev scope without Azure resources. Useful for development environments or Entra-only access structures. #> function New-EntraOpsSubscriptionLandingZone { [OutputType([System.String])] [cmdletbinding()] param( [string[]]$ServiceMembers, [string]$WorkloadPlaneAdmin, [switch]$AddWorkloadPlaneAdminToUsers, [ValidateSet("None", "Eligible", "Permanent")] [string]$GroupOwnership = "None", [string[]]$ControlPlaneAdmins, [string[]]$CatalogPlaneMembers, [switch]$EnablePimForGroups, [switch]$EnableWorkloadPlanePimForGroups, [switch]$CreateM365Group, [switch]$SkipAzureResourceGroup, [switch]$SkipControlPlaneDelegation, [switch]$SkipCatalogOwnerAssignment, [switch]$SkipManagementPlaneDelegation, [ValidateSet("Centralized", "PerService")] [string]$GovernanceModel, [switch]$AllowPerServiceFallback, [string]$AzureRegion, [ValidatePattern('^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')] [string]$SubscriptionId, [string]$DeploymentPrefix = "Default", [ValidateSet("ResourceGroup", "Subscription")] [string]$DeploymentScope = "ResourceGroup", [string]$ControlPlaneDelegationGroupId = "", [string]$ManagementPlaneDelegationGroupId = "", [string]$AdministratorGroupId = "", [string]$ControlPlaneGroupName = "PRG-Tenant-ControlPlane-IdentityOps", [string]$ManagementPlaneGroupName = "PRG-Tenant-ManagementPlane-PlatformOps", [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9_.-]*$')] [string]$GroupPrefix = "SG", [switch]$RoleAssignableWorkloadPlaneAdmins, [string]$logPrefix = "[$($MyInvocation.MyCommand)]" ) begin { $scopeName = if ($DeploymentScope -eq "Subscription") { "Sub" } else { "Rg" } $serviceRoles = @( [pscustomobject]@{accessLevel = ""; name = "Members"; groupType = "Unified"}, [pscustomobject]@{accessLevel = "CatalogPlane"; name = "Members"; groupType = ""}, [pscustomobject]@{accessLevel = "WorkloadPlane"; name = "Users"; groupType = ""}, [pscustomobject]@{accessLevel = "WorkloadPlane"; name = "Admins"; groupType = ""}, [pscustomobject]@{accessLevel = "ControlPlane"; name = "Admins"; groupType = ""} ) Write-Verbose "$logPrefix Deployment scope: $DeploymentScope" # Load EntraOpsConfig.json if not already loaded if ($null -eq $Global:EntraOpsConfig) { $configPaths = @( "$PWD/EntraOpsConfig.json" "$PSScriptRoot/EntraOpsConfig.json" $env:ENTRAOPS_CONFIG ) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } $configLoaded = $false foreach ($configPath in $configPaths) { if (Test-Path $configPath) { try { $Global:EntraOpsConfig = Get-Content -Path $configPath -Raw | ConvertFrom-Json -AsHashtable Write-Verbose "$logPrefix Loaded EntraOpsConfig.json from: $configPath" $configLoaded = $true break } catch { Write-Verbose "$logPrefix Failed to load config from $configPath : $_" } } } if (-not $configLoaded) { Write-Verbose "$logPrefix No EntraOpsConfig.json found. Using parameter defaults." $Global:EntraOpsConfig = @{} } } else { Write-Verbose "$logPrefix Using existing `$Global:EntraOpsConfig" } # Landing zone defaults from EntraOpsConfig; explicit parameters take precedence $configRegion = [string](Get-EntraOpsServiceEMConfigValue -Path 'DefaultAzureRegion') if ([string]::IsNullOrWhiteSpace($AzureRegion) -and -not [string]::IsNullOrWhiteSpace($configRegion)) { $AzureRegion = $configRegion Write-Verbose "$logPrefix Using DefaultAzureRegion '$AzureRegion' from EntraOpsConfig" } foreach ($switchName in 'SkipCatalogOwnerAssignment', 'CreateM365Group', 'AddWorkloadPlaneAdminToUsers', 'RoleAssignableWorkloadPlaneAdmins') { if (-not $PSBoundParameters.ContainsKey($switchName) -and (Get-EntraOpsServiceEMConfigValue -Path $switchName) -eq $true) { Set-Variable -Name $switchName -Value ([switch]$true) Write-Verbose "$logPrefix Using $switchName from EntraOpsConfig" } } $configGroupPrefix = [string](Get-EntraOpsServiceEMConfigValue -Path 'GroupPrefix') if (-not $PSBoundParameters.ContainsKey('GroupPrefix') -and -not [string]::IsNullOrWhiteSpace($configGroupPrefix)) { $GroupPrefix = $configGroupPrefix Write-Verbose "$logPrefix Using GroupPrefix '$GroupPrefix' from EntraOpsConfig" } if (-not $SkipAzureResourceGroup -and $DeploymentScope -ne "Subscription" -and [string]::IsNullOrWhiteSpace($AzureRegion)) { throw "Parameter -AzureRegion (or ServiceEM.DefaultAzureRegion) is required unless -SkipAzureResourceGroup is specified or -DeploymentScope is Subscription." } if (-not $SkipAzureResourceGroup) { if ([string]::IsNullOrWhiteSpace($SubscriptionId)) { throw "Parameter -SubscriptionId is required unless -SkipAzureResourceGroup is specified." } $currentAzTenantId = (Get-AzContext).Tenant.Id if ([string]::IsNullOrWhiteSpace($currentAzTenantId)) { throw "No Azure context found. Sign in with Connect-EntraOps before creating an Azure Resource Group." } # Fail before any Entra object is created if the subscription isn't accessible in the current tenant Get-AzSubscription -SubscriptionId $SubscriptionId -TenantId $currentAzTenantId -ErrorAction Stop | Out-Null } # Read delegation Group IDs and group names from EntraOpsConfig when not supplied as parameters. # A non-empty config value also auto-activates the corresponding skip flag. foreach ($configName in 'ControlPlaneDelegationGroupId', 'ManagementPlaneDelegationGroupId', 'AdministratorGroupId') { $configValue = [string](Get-EntraOpsServiceEMConfigValue -Path $configName) if ([string]::IsNullOrWhiteSpace((Get-Variable -Name $configName -ValueOnly)) -and -not [string]::IsNullOrWhiteSpace($configValue)) { Write-Verbose "$logPrefix Reading $configName from EntraOpsConfig" Set-Variable -Name $configName -Value $configValue } } # Read GovernanceModel from parameter > config > default (PerService) $governanceModelValue = "PerService" $governanceModelSource = "default" $configGovernanceModel = [string](Get-EntraOpsServiceEMConfigValue -Path 'GovernanceModel') if ($PSBoundParameters.ContainsKey('GovernanceModel')) { $governanceModelValue = $GovernanceModel $governanceModelSource = "parameter" } elseif (-not [string]::IsNullOrWhiteSpace($configGovernanceModel)) { $governanceModelValue = $configGovernanceModel $governanceModelSource = "config" } Write-Verbose "$logPrefix Using governance model '$governanceModelValue' (source: $governanceModelSource)" # Centralized removes CatalogPlane-Members, the requestor scope, approver and reviewer fallback of the policies if ($governanceModelValue -eq "Centralized" -and [string]::IsNullOrWhiteSpace($AdministratorGroupId)) { throw "The Centralized governance model requires -AdministratorGroupId (or ServiceEM.AdministratorGroupId in EntraOpsConfig): no per-service CatalogPlane-Members group is created. Use -GovernanceModel PerService or configure the administrator group." } # Auto-resolve or create role-assignable delegation groups. # Searches by config ID, then by default group name, then creates if permissions allow. # Group names from config take precedence over the parameter defaults. foreach ($configName in 'ControlPlaneGroupName', 'ManagementPlaneGroupName') { $configValue = [string](Get-EntraOpsServiceEMConfigValue -Path $configName) if (-not [string]::IsNullOrWhiteSpace($configValue)) { Set-Variable -Name $configName -Value $configValue } } if ($governanceModelValue -eq "Centralized") { # Centralized model: Use tenant-wide delegation groups Write-Verbose "$logPrefix Centralized governance model - using tenant-wide delegation groups" # Falls back to PerService only with -AllowPerServiceFallback $centralizedFailed = $false $centralizedError = $null try { $ControlPlaneDelegationGroupId = Resolve-EntraOpsServiceEMDelegationGroup ` -Plane "ControlPlane" ` -GroupId $ControlPlaneDelegationGroupId ` -DefaultGroupName $ControlPlaneGroupName ` -ConfigKey "ControlPlaneDelegationGroupId" ` -logPrefix $logPrefix $SkipControlPlaneDelegation = $true } catch { $centralizedFailed = $true $centralizedError = $_ Write-Warning "$logPrefix Failed to resolve ControlPlane delegation group: $_" } if (-not $centralizedFailed) { try { $ManagementPlaneDelegationGroupId = Resolve-EntraOpsServiceEMDelegationGroup ` -Plane "ManagementPlane" ` -GroupId $ManagementPlaneDelegationGroupId ` -DefaultGroupName $ManagementPlaneGroupName ` -ConfigKey "ManagementPlaneDelegationGroupId" ` -logPrefix $logPrefix $SkipManagementPlaneDelegation = $true } catch { $centralizedFailed = $true $centralizedError = $_ Write-Warning "$logPrefix Failed to resolve ManagementPlane delegation group: $_" } } if ($centralizedFailed -and -not $AllowPerServiceFallback) { throw "$logPrefix The Centralized governance model failed to resolve the tenant-wide delegation groups; nothing was created. Create the delegation groups and set their IDs in EntraOpsConfig.json (ServiceEM.ControlPlaneDelegationGroupId / ServiceEM.ManagementPlaneDelegationGroupId), use -GovernanceModel PerService, or pass -AllowPerServiceFallback to create per-service ControlPlane-Admins and ManagementPlane-Admins groups instead. Error: $centralizedError" } if ($centralizedFailed) { Write-Warning "$logPrefix ==========================================" Write-Warning "$logPrefix CENTRALIZED GOVERNANCE MODEL FAILED" Write-Warning "$logPrefix ==========================================" Write-Warning "$logPrefix Falling back to PerService governance model (-AllowPerServiceFallback)." Write-Warning "$logPrefix PerService will create per-service admin groups instead." Write-Warning "$logPrefix " Write-Warning "$logPrefix To use Centralized model, either:" Write-Warning "$logPrefix 1. Create delegation groups manually and add IDs to EntraOpsConfig.json" Write-Warning "$logPrefix 2. Grant RoleManagement.ReadWrite.Directory permission for auto-creation" Write-Warning "$logPrefix ==========================================" # Reset delegation group IDs to trigger per-service creation $ControlPlaneDelegationGroupId = "" $ManagementPlaneDelegationGroupId = "" $SkipControlPlaneDelegation = $false $SkipManagementPlaneDelegation = $false $governanceModelValue = "PerService" } else { # Remove per-service ControlPlane, ManagementPlane, CatalogPlane groups from ServiceRoles Write-Verbose "$logPrefix Removing ControlPlane/ManagementPlane/CatalogPlane from per-service groups" $serviceRoles = @($serviceRoles | Where-Object { -not (($_.accessLevel -eq "ControlPlane" -and $_.name -eq "Admins") -or ($_.accessLevel -eq "ManagementPlane" -and $_.name -eq "Admins") -or ($_.accessLevel -eq "CatalogPlane" -and $_.name -eq "Members")) }) } } else { # PerService model: Keep per-service groups, but still resolve delegation if IDs provided Write-Verbose "$logPrefix PerService governance model - creating per-service admin groups" if (-not [string]::IsNullOrWhiteSpace($ControlPlaneDelegationGroupId)) { $ControlPlaneDelegationGroupId = Resolve-EntraOpsServiceEMDelegationGroup ` -Plane "ControlPlane" ` -GroupId $ControlPlaneDelegationGroupId ` -DefaultGroupName $ControlPlaneGroupName ` -ConfigKey "ControlPlaneDelegationGroupId" ` -logPrefix $logPrefix $SkipControlPlaneDelegation = $true } if (-not [string]::IsNullOrWhiteSpace($ManagementPlaneDelegationGroupId)) { $ManagementPlaneDelegationGroupId = Resolve-EntraOpsServiceEMDelegationGroup ` -Plane "ManagementPlane" ` -GroupId $ManagementPlaneDelegationGroupId ` -DefaultGroupName $ManagementPlaneGroupName ` -ConfigKey "ManagementPlaneDelegationGroupId" ` -logPrefix $logPrefix $SkipManagementPlaneDelegation = $true } } # Add ManagementPlane-Admins unless it is delegated. if (-not $SkipManagementPlaneDelegation) { $serviceRoles += [pscustomobject]@{accessLevel = "ManagementPlane"; name = "Admins"; groupType = ""} } if ($SkipControlPlaneDelegation) { Write-Verbose "$logPrefix Removing ControlPlane components from ServiceRoles" $serviceRoles = @($serviceRoles | Where-Object { $_.accessLevel -ne "ControlPlane" }) } # Log final switch states for troubleshooting Write-Verbose "$logPrefix ==========================================" Write-Verbose "$logPrefix FINAL DELEGATION SWITCH STATES:" Write-Verbose "$logPrefix SkipControlPlaneDelegation: $SkipControlPlaneDelegation" Write-Verbose "$logPrefix SkipManagementPlaneDelegation: $SkipManagementPlaneDelegation" Write-Verbose "$logPrefix ControlPlaneDelegationGroupId: $(if ($ControlPlaneDelegationGroupId) { 'SET' } else { 'NOT SET' })" Write-Verbose "$logPrefix ManagementPlaneDelegationGroupId: $(if ($ManagementPlaneDelegationGroupId) { 'SET' } else { 'NOT SET' })" Write-Verbose "$logPrefix GovernanceModel: $governanceModelValue" Write-Verbose "$logPrefix ==========================================" } process { Write-Verbose "$logPrefix Processing LZ" $splatServiceBootstrap = @{ ServiceName = "$scopeName-$DeploymentPrefix" GroupPrefix = $GroupPrefix AddWorkloadPlaneAdminToUsers = $AddWorkloadPlaneAdminToUsers RoleAssignableWorkloadPlaneAdmins = $RoleAssignableWorkloadPlaneAdmins EnablePimForGroups = $EnablePimForGroups EnableWorkloadPlanePimForGroups = $EnableWorkloadPlanePimForGroups CreateM365Group = $CreateM365Group GroupOwnership = $GroupOwnership AzureRegion = $AzureRegion ServiceRoles = $serviceRoles SkipControlPlaneDelegation = $SkipControlPlaneDelegation SkipCatalogOwnerAssignment = $SkipCatalogOwnerAssignment SkipManagementPlaneDelegation = $SkipManagementPlaneDelegation ControlPlaneDelegationGroupId = $ControlPlaneDelegationGroupId ManagementPlaneDelegationGroupId = $ManagementPlaneDelegationGroupId AdministratorGroupId = $AdministratorGroupId SkipAzureResourceGroup = $SkipAzureResourceGroup AzureScope = $DeploymentScope } if (-not $SkipAzureResourceGroup) { $splatServiceBootstrap.SubscriptionId = $SubscriptionId } foreach ($optional in 'ServiceMembers', 'CatalogPlaneMembers') { if ($PSBoundParameters.ContainsKey($optional)) { $splatServiceBootstrap[$optional] = Get-Variable -Name $optional -ValueOnly } } if (-not [string]::IsNullOrWhiteSpace($WorkloadPlaneAdmin)) { $splatServiceBootstrap.WorkloadPlaneAdmin = $WorkloadPlaneAdmin } if ($PSBoundParameters.ContainsKey('ControlPlaneAdmins')) { if ($serviceRoles | Where-Object { $_.accessLevel -eq "ControlPlane" -and $_.name -eq "Admins" }) { $splatServiceBootstrap.ControlPlaneAdmins = $ControlPlaneAdmins } else { Write-Warning "$logPrefix No per-service ControlPlane-Admins group is created (governance model '$governanceModelValue' or delegated ControlPlane); -ControlPlaneAdmins is ignored" } } return New-EntraOpsServiceBootstrap @splatServiceBootstrap } } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBGcRMaTNGuDLKS # LtoJxHKX5BYvEOmgAOk3vqOEAkaqU6CCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK # ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy # dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l # dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG # A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl # BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb # Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC # 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va # qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ # 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet # /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe # GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV # WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl # s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb # MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc # noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl # jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA # AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8 # +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud # DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w # bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v # c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np # dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG # CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM # BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1 # BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR # mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl # lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs # NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv # n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggZrMIIEU6ADAgECAhBX5gfP8LabvnxC # XoItEBa/MA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB # c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp # Z25pbmcgMjAyMSBDQTAeFw0yNjEwMDYwODI4MjNaFw0yNzEwMDYwODI4MjJaMIGQ # MQswCQYDVQQGEwJERTEdMBsGA1UECAwUUmhpbmVsYW5kLVBhbGF0aW5hdGUxEjAQ # BgNVBAcMCUxhaG5zdGVpbjEeMBwGA1UECgwVT3BlbiBTb3VyY2UgRGV2ZWxvcGVy # MS4wLAYDVQQDDCVPcGVuIFNvdXJjZSBEZXZlbG9wZXIgVGhvbWFzIE5hdW5oZWlt # MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAphp2ljSmVsRB6gvkEq7A # DUMENpKZiDFoAfF1ktD1nCwaVyI+bJ0ROjHwAn4iJgS+vlBDPOUIw2GLHkiqUhOg # HSwzg6gj45GNmt6zxsKP0zPlhMB/fb0zmz7/F7e3yfKCXLfhbHuk0Es9gJ+8Wmet # 9StVwwSE/K0MpW06GKA9nPWyE4N/ADwigDIj9rsTKJR4cve9w8G1b3zohpUc/qMr # onab+HVw5r9b3sOBnnaXC6zvrX4fovRz1ztwfWjRjILLoFG6mW/TuK5F2ICD3Rhl # 9R2rJWWWhzTDMsQlzvX+/SaM74q3OFpIVNdhvtmWU9pKUBRAyYMijcDqKEFGSzrc # 8JFmGMpP5TuroWF+N21d3px2eARvW1mV0DCfFAH+Og20SH/gVW6/ltQQEkH/z/Zt # 49ARyJrgESLOQtlMCIYSoKISdkk4ml4VUS1E/PcbzXEYj3a7m6DOxcd3WuY03UT9 # cnk3p0LMN6LeLYAZgVY+HRloZlH+kW2SkOHNuWgtXvZhAgMBAAGjggF4MIIBdDAM # BgNVHRMBAf8EAjAAMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jY3NjYTIwMjEu # Y3JsLmNlcnR1bS5wbC9jY3NjYTIwMjEuY3JsMHMGCCsGAQUFBwEBBGcwZTAsBggr # BgEFBQcwAYYgaHR0cDovL2Njc2NhMjAyMS5vY3NwLWNlcnR1bS5jb20wNQYIKwYB # BQUHMAKGKWh0dHA6Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9jY3NjYTIwMjEuY2Vy # MB8GA1UdIwQYMBaAFN10XUwA23ufoHTKsW73PMAywHDNMB0GA1UdDgQWBBTBvGyH # MXj1dMbVnxuof9oP4PY7cDBLBgNVHSAERDBCMAgGBmeBDAEEATA2BgsqhGgBhvZ3 # AgUBBDAnMCUGCCsGAQUFBwIBFhlodHRwczovL3d3dy5jZXJ0dW0ucGwvQ1BTMBMG # A1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF # AAOCAgEAkC165c7/r6y3VeEEpNXas9bQs6ZpKPdJffHkf9xoPi4kWvAIWGc4ocG1 # DJLb18RXiDYShDmT5sb/6I08FeipKi2DNJkuOq6yOTJ3S7+hNFIZCyd6i7WKO8ea # P5I+FAbyJP4+5coZoMg45bUa9JoocaJCm+5n7WiCfN7/EQpClW5Trm4+ZMbp7EMN # F1uLJgGCMnN0SsoUarp7PHlqupMxMC4Uhhe+lAP2ytcSIU81L97/qjrADbLWqICk # 9il5iGM+aZndguq/cmfH9rJj4GRZHWnG6WwnzYD9+KgYlRI6em+/9dq+IRiXe2Bs # thUuIaFW7U2gWw8tBTEYm1UBLHdJx+L1gjJCLs5KT/wy5wU32wZxNDqPUQejUC1x # GvU3cKqmLgRq+yNf3gFVfzqIoggyM3U9wQvnk7nBtCkpjXS2hdx4yxzcmrlWsDHr # z6+6/jeyQySzbYUlFmKnc4n9Mw2soHOajEua5poFARVRS5KgUBuiiQ21YFNYzWBo # qGuECaaAI/C5rGxapMJ3dAOBnHw6NLqBet+ZXBJEccX5y89MLlOQu+jDmJP8HieR # qDLlwfhVZnw4RY8Rus43NZBwhNlVuOUXqocHKmJhKNlekda3qsJj93eQBQOxKJcc # w5O08q9jukpH2+yzFAj+7ll33DuNUgWptogc4Awtbx3glzcEvJQwggaCMIIEaqAD # AgECAhAo8HfBHDa9/l90MkdwJy4DMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYT # AlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMT # G0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQTAeFw0yNjAzMTEwNzM0NTRaFw0z # NjAyMjcwNzM0NTRaMFAxCzAJBgNVBAYTAlBMMSEwHwYDVQQKDBhBc3NlY28gRGF0 # YSBTeXN0ZW1zIFMuQS4xHjAcBgNVBAMMFUNlcnR1bSBUaW1lc3RhbXAgMjAyNjCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi9zfBw+xI12hi2F9uNQBNO # STkTIi13fuh3DiWybI5392bBfJw6tO7zDs7UqP/rejVf4xFTU3XNOPsuOFkP5SPF # snWwn3VJL+NhNhsuujvYsQtaQnvNo1wk46reTgws1L8OFEdKaxgyqT4zOQx81mLw # NZSqXNU9Rrb9oAJWJF4Mydz+C1ebu0D0+vgM3tqAQ8TU203fqgKCPqIdHloaFqH9 # eyR+8tnpmy2hnnN59ZOtmsuHeRwf5iTOhlhf0d/qqquLOSIGPgGVL1Hj5XBUox2O # E7tQRr+l/xxNbL6OuwBO4Aa2EbuxYg37cpXv5i2/lTL4NASB/duf9W4fjA3Ro+GP # +bC54n2OYiM3Cq8z92ejx8F5HWO2e4/75eC95B9tMw/eq7109GDyW2+2ut5Bn0o8 # MXJTERkyystWbqcnUkomA97qfiDCwvpWuOMdfTOksXe9S+xDxOJaCbiyTivgPulx # cT+tfQTRXBr5UwtPvc+INnafOEilzVHA11tv+pHcylryxBc08cUihf/ttqkZS0bm # x3VxQay8uohaTp0//ECA9vYGW51SKdJMJhXvKyIdBLy8WYNchTt8yRITv9Adxg9J # qGujh+TnP1bW3aQjFtlEl7SzgNeI8OVA/CJAWWUuRT3Au9ToOpHy/flrrknhN4he # Ew7/6ILMv4HSsj0YZ4otAgMBAAGjggFQMIIBTDB1BggrBgEFBQcBAQRpMGcwOwYI # KwYBBQUHMAKGL2h0dHA6Ly9zdWJjYS5yZXBvc2l0b3J5LmNlcnR1bS5wbC9jdHNj # YTIwMjEuY2VyMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u # Y29tMB8GA1UdIwQYMBaAFL5UAi+/QGxzQ86sCSVOnkNEGu7gMAwGA1UdEwEB/wQC # MAAwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL3N1YmNhLmNybC5jZXJ0dW0ucGwv # Y3RzY2EyMDIxLmNybDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E # BAMCB4AwIgYDVR0gBBswGTAIBgZngQwBBAIwDQYLKoRoAYb2dwIFAQswHQYDVR0O # BBYEFCM5aiirmhKnpKfyH/lFmr+N/oIDMA0GCSqGSIb3DQEBDAUAA4ICAQBm/pOb # rAveTqYFPCSthxNVwBHTW4TfuBQY2KGCdMlH6ALjVPYf2XARNGMTXScR0JQ2/c2L # xLpoHHhRwxFf3d933DhFS165rYj6iPSQKm41rc9AMpTCjMdmai1aK9o870pOzTzL # RhWFVIxm+5Qf+t4V5z14tPoUsAWdfRKD1Zzvzfe5AHlE9rxOZcGROiULxs3Edf7Z # J8RZI0jzD9juj2oEX2XS7BVKuNCK1W0h1mjapv028yqo8UtZh5/1Ib48Yyp88BDY # Shh5PSGUUuS5tYXVfcduIPBe3PdNTYRYdT5VNxkwLgjMzIcr/6SH2fejFJDjnCxj # vXLuarErJg/IdI+hXdGIBDVOGv1zrkF4ktoQvFZIp5DIAYh0GwyUhc87qqn5UaYT # j2+iyiBDSNh7aGCrkuAnqg7Qh7frhX+QhMGeJ9SpLpnG76qN85hLyjvYR6gRYiLT # j8G4fxjqV400/buPw0rBo9yJZyq/8cGdCOLPOYf9qpefTcrZKeO9pg+UJHx8UABb # aTqizRxm8sUSEycccCCFhIit3AI/3wGuSOADeiA676NcQ7+dsP04YvzvdN7pTZNy # g672WpBDJdg0eItHl4yomvNeIOWh1qs4QqOW6TI8Ocsi4uSDTLorxLUDnBTwKQzz # pxd1YUctEgRh32R0J9FkFkeEruJU94RljFr1uDCCBrkwggShoAMCAQICEQCZo4AK # JlU7ZavcboSms+o5MA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAG # A1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVt # IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3Rl # ZCBOZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjE4WhcNMzYwNTE4MDUzMjE4WjBW # MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu # MSQwIgYDVQQDExtDZXJ0dW0gQ29kZSBTaWduaW5nIDIwMjEgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQCdI88EMCM7wUYs5zNzPmNdenW6vlxNur3r # Lfi+5OZ+U3iZIB+AspO+CC/bj+taJUbMbFP1gQBJUzDUCPx7BNLgid1TyztVLn52 # NKgxxu8gpyTr6EjWyGzKU/gnIu+bHAse1LCitX3CaOE13rbuHbtrxF2tPU8f253Q # gX6eO8yTbGps1Mg+yda3DcTsOYOhSYNCJiL+5wnjZ9weoGRtvFgMHtJg6i671OPX # IciiHO4Lwo2p9xh/tnj+JmCQEn5QU0NxzrOiRna4kjFaA9ZcwSaG7WAxeC/xoZSx # F1oK1UPZtKVt+yrsGKqWONoK6f5EmBOAVEK2y4ATDSkb34UD7JA32f+Rm0wsr5aj # zftDhA5mBipVZDjHpwzv8bTKzCDUSUuUmPo1govD0RwFcTtMXcfJtm1i+P2UNXad # PyYVKRxKQATHN3imsfBiNRdN5kiVVeqP55piqgxOkyt+HkwIA4gbmSc3hD8ke66t # 9MjlcNg73rZZlrLHsAIV/nJ0mmgSjBI/TthoGJDydekOQ2tQD2Dup/+sKQptalDl # ui59SerVSJg8gAeV7N/ia4mrGoiez+SqV3olVfxyLFt3o/OQOnBmjhKUANoKLYlK # mUpKEFI0PfoT8Q1W/y6s9LTI6ekbi0igEbFUIBE8KDUGfIwnisEkBw5KcBZ3XwnH # mfznwlKo8QIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU # 3XRdTADbe5+gdMqxbvc8wDLAcM0wHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61Pgc # ptE6Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDAGA1Ud # HwQpMCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYI # KwYBBQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0 # dW0uY29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwv # Y3RuY2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0 # cDovL3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQB1iFgP5Y9Q # KJpTnxDsQ/z0O23JmoZifZdEOEmQvo/79PQg9nLF/GJe6ZiUBEyDBHMtFRK0mXj3 # Qv3gL0sYXe+PPMfwmreJHvgFGWQ7XwnfMh2YIpBrkvJnjwh8gIlNlUl4KENTK5DL # qsYPEtRQCw7R6p4s2EtWyDDr/M58iY2UBEqfUU/ujR9NuPyKk0bEcEi62JGxauFY # zZ/yld13fHaZskIoq2XazjaD0pQkcQiIueL0HKiohS6XgZuUtCKA7S6CHttZEsOb # QJ1j2s0urIDdqF7xaXFVaTHKtAuMfwi0jXtF3JJphrJfc+FFILgCbX/uYBPBlbBI # P4Ht4xxk2GmfzMn7oxPITpigQFJFWuzTMUUgdRHTxaTSKRJ/6Uh7ki/pFjf9sUAS # WgxT69QF9Ki4JF5nBIujxZ2sOU9e1HSCJwOfK07t5nnzbs1LbHuAIGJsRJiQ6HX/ # DW1XFOlXY1rc9HufFhWU+7Uk+hFkJsfzqBz3pRO+5aI6u5abI4Qws4YaeJH7H7M8 # X/YNoaArZbV4Ql+jarKsE0+8XvC4DJB+IVcvC9Ydqahi09mjQse4fxfef0L7E3hh # o2O3bLDM6v60rIRUCi2fJT2/IRU5ohgyTch4GuYWefSBsp5NPJh4QRTP9DC3gc5Q # EKtbrTY0Ka87Web7/zScvLmvQBm8JDFpDjCCBrkwggShoAMCAQICEQDn/2nHOzXO # S5Em2HR8aKWHMA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UE # ChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl # cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBO # ZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjA3WhcNMzYwNTE4MDUzMjA3WjBWMQsw # CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQw # IgYDVQQDExtDZXJ0dW0gVGltZXN0YW1waW5nIDIwMjEgQ0EwggIiMA0GCSqGSIb3 # DQEBAQUAA4ICDwAwggIKAoICAQDpEh8ENe25XXrFppVBvoplf0530W0lddNmjtv4 # YSh/f7eDQKFaIqc7tHj7ox+u8vIsJZlroakUeMS3i3T8aJRC+eQs4FF0GqvkM6+W # ZO8kmzZfxmZaBYmMLs8FktgFYCzywmXeQ1fEExflee2OpbHVk665eXRHjH7MYZIz # Nnjl2m8Hy8ulB9mR8wL/W0v0pjKNT6G0sfrx1kk+3OGosFUb7yWNnVkWKU4qSxLv # 16kJ6oVJ4BSbZ4xMak6JLeB8szrK9vwGDpvGDnKCUMYL3NuviwH1x4gZG0JAXU3x # 2pOAz91JWKJSAmRy/l0s0l5bEYKolg+DMqVhlOANd8Yh5mkQWaMEvBRE/kAGzIqg # WhwzN2OsKIVtO8mf5sPWSrvyplSABAYa13rMYnzwfg08nljZHghquCJYCa/xHK9a # cev9UD7Y+usr15d7mrszzxhF1JOr1Mpup2chNSBlyOObhlSO16rwrffVrg/SzaKf # SndS5swRhr8bnDqNJY9TNyEYvBYpgF95K7p0g4LguR4A++Z1nFIHWVY5v0fNVZmg # zxD9uVo/gta3onGOQj3JCxgYx0KrCXu4yc9QiVwTFLWbNdHFSjBCt5/8Q9pLuRhV # ocdCunhcHudMS1CGQ/Rn0+7P+fzMgWdRKfEOh/hjLrnQ8BdJiYrZNxvIOhM2aa3z # EDHNwwIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvlQC # L79AbHNDzqwJJU6eQ0Qa7uAwHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61PgcptE6 # Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMDAGA1UdHwQp # MCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYIKwYB # BQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u # Y29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY3Ru # Y2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0cDov # L3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQC4k1l3yUwV/ZQH # CKCneqAs8EGTnwEUJLdDpokN/dMhKjK0rR5qX8nIIHzxpQR3TAw2IRw1Uxsr2Pli # G3bCFqSdQTUbfaTq6V3vBzEebDru9QFjqlKnxCF2h1jhLNFFplbPJiW+JSnJTh1f # KEqEdKdxgl9rVTvlxfEJ7exOn25MGbd/wGPwuSmMxRJVO0wnqgS7kmoJjNF9zqee # hFSDDP8ZVkWg4EZ2tIS0M3uZmByRr+1Lkwjjt8AtW83mVnZTyTsOb+FNfwJY7DS4 # FmWhkRbgcHRetreoTirPOr/ozyDKhT8MTSTf6Lttg6s6T/u08mDWw6HK04ZRDfQ9 # sb77QV8mKgO44WGP31vXnVKoWVJpFBjPvjL8/Zck/5wXX2iqjOaLStFOR/IQki+E # hn4zlcgVm22ZVCBPF+l8nAwUUShCtKuSU7GmZLKCmmxQMkSiWILTm8EtVD6AxnJh # oq8EnhjEEyUoflkeRF2WhFiVQOmWTwZRr44IxWGkNJC6tTorW5rl2Zl+2e9JLPYf # 3pStAPMDoPKIjVXd6NW2+fZrNUBeDo2eOa5Fn7Brs/HLQff5Xgris5MeUbdVgDrF # 8uxO6cLPvZPo63j62SsNg55pTWk9fUIF9iPoRbb4QurjoY/woI1RAOKtYtTic6aA # Jq3u83RIPpGXBSJKwx4KJAOZnCDCtTGCBqIwggaeAgEBMGowVjELMAkGA1UEBhMC # UEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMb # Q2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBAhBX5gfP8LabvnxCXoItEBa/MA0G # CWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZI # hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC # ARUwLwYJKoZIhvcNAQkEMSIEIIuFrkA7sVchfyUEB2GZO2xDfVgEJiLt18CtN43e # kPWXMA0GCSqGSIb3DQEBAQUABIIBgFCobUk18M6hnXvAdXnUBxQoKMnV8oKXtoW9 # dY5awXU0Z0AQhEeB8Z6qXUeLCqTDcNNJy8DtYQwmw7DfenqsLM/nCvKZx9YTd7d+ # nM9EVYcqgfj6+XANFSJUMnlA8k9NdoLM5DKvTgMyr44FJ6r8FlffR1pPmUHcrmvJ # jH2KuhPVc17mixAxw3L+KFTVUT89ajfaIHxIWjan91T+NkitdLNMOudLxRBVayFj # zNmXamvSRWE80EKtAtbD6ae944YMcAMDZ5UWyrUdTlMa93B5sdGWRZjpeg8FW6zO # SbzR9klrM22daadezxG/GfnU2obyg5dL87OHW5qebB5aNvKfUNik9JmOUmgZj3J3 # P+0Bv4+UQMBO8oUNfKS1ReJgqXm1I+q/yrSwTLjbU8Xogo5NkKvSeqN0af06DQdT # Gera2cN/bGLocED8YqRRPMldc8KYF5thDYSS9DWsknxyZwGtSaP6XUnlKrcaiexk # 8Mp7lsCv3Z3HgHgOWkMBBPY/KR0k+KGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMjcwNTQxMTdaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDCKnXDS2542RSn7EhOTKPHAUr6OZ7hbOrp4 # JI2LZDM4LBzYL4AVEEDJz24jg9ZnV14wgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIARn1NrhpHj0biIfx/OKTTkcSER5YaWE3GoTIE4CJ5M2cP7Hdu # Vu/e/2Xs/JRgCFhi4W9JUqDtdO5gYP0K8kPKYmPaBij+0KFO5caRxGSnrnQk46lo # ttvwHJtkqIidxMmr6DwcrsLH0QRjRWZhBVLxXVwjmxxsiq04STvTJhEOlBEFenBb # 07g8P6pawEnXU9d4y0D5odi7HgtE0ZT8FfbsRnbHaEh1+cW2zue/DYnVN46QpPKh # +fugJseTA80JBtIJEpZkvvjHWcNW7AdvtJiOy7yVelYde3B8JuDIW/Jl0rP2knio # VcAJG9OQF2WDcJrQswBQm18zXt/otK5hSlMapyuT2nfIJyOJG2dQIPSQ4rGl6eMN # iJ/R84+O/5c3qRYMCEUr1srHAwIHJYtyKy4vEpEZZwMSOo47zMNotLtPW2jCjZwb # 36Fi1jfXSH3csp1+f8jNTYVXGC/hDETM8dTsx6dB7f9TswPrIfUqzdUwt2ERf2wd # YDmVfd8nY7q+3T/j5qpBtX3V70R08TsrUhVcEJ6Q44H1vzGFrzszpcbBh6u6AQhr # /DiX5BPwE0d8gCWiYp530N4zkyyspbaipnWibWjrClsZSl0zosRQCEwzlp/zOzPd # qBzYPoctBLIo1h9Nt5cRC7xb3ufxZn+WEpKTfu1FXF8MeQjuAP48l2QP7fY= # SIG # End signature block |