Public/ServiceEM/Remove-EntraOpsServiceCatalog.ps1
|
<# .SYNOPSIS Removes an Entitlement Management catalog, all its contents, the associated Entra groups, and the Azure resource group created by the landing zone. .DESCRIPTION Performs a complete teardown of a ServiceEM landing zone: 1. Cancels open assignment requests of the access packages (e.g. pending approval) and waits for requests that can't be canceled (e.g. delivering), because Graph rejects a removal while a request is open. 2. Revokes all active (non-expired) access package assignments via adminRemove requests and verifies successful delivery and the absence of active assignments. 3. Deletes all access packages in the catalog, retrying only InvalidDeleteEntitlementHasGrants for up to 60 seconds if the grants index has not caught up. If a request doesn't complete, a removal or an access package deletion fails, the cmdlet stops with an error and keeps the catalog, the groups and the Azure resource group, so a re-run can finish the removal. 4. Deletes the Entra groups registered as catalog resources that were created by the landing zone (mailNickname "<ServiceName>.*", ServiceName = catalog name without "Catalog-"). Other catalog resources, e.g. shared groups added manually, are kept, as well as any group whose Object ID appears in ExcludeGroupIds. A failed group deletion doesn't stop the removal of the other groups. 5. Deletes the catalog itself, only if all groups were deleted; otherwise the catalog is kept so a re-run can find it and finish the removal. 6. Only with -RemoveAzureResourceGroup: deletes the Azure resource group created for the service in -SubscriptionId if it exists: RG-<ServiceName> with a leading "Rg-" removed (e.g. Catalog-Rg-MyApp -> RG-MyApp), only if it has the tag EntraOpsServiceEM = <ServiceName> set by New-EntraOpsServiceAZContainer. Subscription scope catalogs (Catalog-Sub-*) never delete a resource group; their Azure role assignments on the subscription must be removed manually. Failures of steps 4 and 5 are collected and reported in one error at the end. Displays progress during teardown and prints a completion summary to the information stream. Set $ProgressPreference = 'SilentlyContinue' to hide progress, and pass -InformationAction Ignore to hide the summary. The pipeline return value remains an empty hashtable. Asks for confirmation unless -Force or -Confirm:$false is passed; supports -WhatIf. Intended for decommissioning services provisioned by New-EntraOpsServiceBootstrap. .PARAMETER ServiceCatalogName Display name of the catalog to remove (e.g. "Catalog-MyService"). .PARAMETER Force Skips the confirmation prompt. Without -Force (and without -Confirm:$false) the cmdlet asks for confirmation before deleting anything. Use -WhatIf to show what would be deleted. .PARAMETER ExcludeGroupIds Object IDs of Entra groups that must not be deleted even if they are registered as catalog resources. Use this to protect shared delegation groups (ControlPlane-Admins, ManagementPlane-Admins, CatalogPlane-Members) that are reused across multiple services. .PARAMETER RemoveAzureResourceGroup Opt-in: also deletes the Azure resource group of the service including all its resources. Requires -SubscriptionId. Without this switch the resource group is kept. A resource group without the tag EntraOpsServiceEM = <ServiceName> (e.g. an existing one reused by the landing zone) is always kept. .PARAMETER SubscriptionId Subscription of the resource group deleted with -RemoveAzureResourceGroup. Required so that a resource group with the same name in the subscription of the current Azure context isn't deleted by mistake; the previous Azure context is restored afterwards. .PARAMETER logPrefix Text prepended to verbose messages. Defaults to the function name. .EXAMPLE Remove-EntraOpsServiceCatalog -ServiceCatalogName "Catalog-MyService" -Force Revokes all active assignments, deletes all access packages, removes the catalog and deletes all associated Entra groups. The Azure RG is kept. .EXAMPLE Remove-EntraOpsServiceCatalog -ServiceCatalogName "Catalog-MyService" -Force ` -ExcludeGroupIds @( "00000000-0000-0000-0000-000000000001", # ControlPlane-Admins (shared) "00000000-0000-0000-0000-000000000002" # ManagementPlane-Admins (shared) ) Same as above but skips deletion of the two shared delegation groups. .EXAMPLE Remove-EntraOpsServiceCatalog -ServiceCatalogName "Catalog-Rg-MyService" -Force -RemoveAzureResourceGroup ` -SubscriptionId "<subscription-id>" Removes all Entra ID / EM artifacts and also deletes the Azure resource group RG-MyService. .EXAMPLE Remove-EntraOpsServiceCatalog -ServiceCatalogName "Catalog-MyService" -WhatIf Shows the catalog, access packages and groups that would be deleted without changing anything. #> function Remove-EntraOpsServiceCatalog { [OutputType([hashtable])] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( [Parameter(Mandatory)] [string]$ServiceCatalogName, [switch]$Force, [string[]]$ExcludeGroupIds = @(), [switch]$RemoveAzureResourceGroup, [ValidatePattern('^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')] [string]$SubscriptionId, [string]$logPrefix = "[$($MyInvocation.MyCommand)]" ) begin { $progressActivity = $null $progressId = 47002 } process { if ($RemoveAzureResourceGroup -and [string]::IsNullOrWhiteSpace($SubscriptionId)) { throw "-RemoveAzureResourceGroup requires -SubscriptionId (subscription of the landing zone resource group)." } if ($Force -and -not $WhatIfPreference) { $ConfirmPreference = 'None' } $encodedServiceCatalogName = ConvertTo-EntraOpsODataStringLiteral -Value $ServiceCatalogName $catalogs = @(Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/identityGovernance/entitlementManagement/catalogs?`$filter=displayName eq '$encodedServiceCatalogName'&`$expand=accessPackages,resources" -OutputType PSObject -DisableCache -ThrowOnFailure | Where-Object { $null -ne $_ }) if ($catalogs.Count -eq 0) { Write-Warning "$logPrefix Unable to obtain catalog by name" return @{} } # Never enumerate packages or groups from an ambiguous name lookup during teardown. if ($catalogs.Count -gt 1) { throw "$logPrefix Found $($catalogs.Count) catalogs named '$ServiceCatalogName'; no resources were removed. Use a unique catalog name before retrying." } $catalog = $catalogs[0] if ([string]::IsNullOrWhiteSpace([string]$catalog.Id)) { throw "$logPrefix Catalog '$ServiceCatalogName' has no object ID; no resources were removed." } Write-Verbose "$logPrefix Obtained Service Catalog $($catalog.Id)" $groupResourceCount = @($catalog.Resources | Where-Object { $_.OriginSystem -eq "AadGroup" }).Count $target = "$ServiceCatalogName ($(@($catalog.accessPackages).Count) access packages, $groupResourceCount group resources$(if ($RemoveAzureResourceGroup) { ', Azure resource group' }))" if (-not $PSCmdlet.ShouldProcess($target, "Remove assignments, access packages, catalog and landing zone groups")) { return @{} } $progressActivity = "Removing ServiceEM: $ServiceCatalogName" Write-Progress -Id $progressId -Activity $progressActivity -Status 'Checking open access requests' $azureCleanupNeedsAttention = $false $azureSummary = 'Azure resource group kept' $terminalStates = @("fulfilled", "delivered", "canceled", "deliveryfailed", "denied", "completed", "partiallydelivered", "dropped") $cancellableStatuses = @("accepted", "pendingApproval", "pendingNotBefore", "pendingApprovalEscalated") $failures = [System.Collections.Generic.List[string]]::new() function Test-OpenRequest ($Request) { if ($null -eq $Request) { return $false } if (-not $Request.id) { throw "Unable to verify assignment request; the response has no request ID." } # State is authoritative; status supplies detail and is only a fallback for older responses. $state = if ($Request.state) { $Request.state } else { $Request.status } $terminalStates -notcontains $state } function Stop-IncompleteRemoval { if ($failures.Count -gt 0) { throw "$logPrefix Removal of '$ServiceCatalogName' is incomplete; the catalog, its groups and the Azure resource group were kept. Resolve the following and run the cmdlet again: $($failures -join '; ')" } } function Wait-OpenRequest ([string[]]$RequestIds, [string]$Activity) { $check = @{ Pending = @($RequestIds); Requests = @() } $null = Wait-EntraOpsServiceEMCondition -Activity $Activity -logPrefix $logPrefix -ProgressStatus { "$($check.Pending.Count) of $($RequestIds.Count) request(s) pending" } -Condition { $check.Requests = @($RequestIds | ForEach-Object { $request = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/identityGovernance/entitlementManagement/assignmentRequests/$_" -OutputType PSObject -DisableCache -ThrowOnFailure if (-not $request -or $request.id -ne $_) { throw "Unable to verify assignment request $_; no matching request was returned." } $request }) $check.Pending = @($check.Requests | Where-Object { Test-OpenRequest $_ }) $details = ($check.Requests | ForEach-Object { "$($_.id) (state '$($_.state)', status '$($_.status)')" }) -join ', ' Write-Verbose "$logPrefix ${Activity}: $($check.Pending.Count) pending of $($RequestIds.Count); $details" $check.Pending.Count -eq 0 } return $check } # Step 1: Cancel open requests (e.g. pending approval) or wait for them; Graph rejects adminRemove while one is open. $openRequestIds = [System.Collections.Generic.List[string]]::new() foreach ($accessPackage in $catalog.accessPackages) { $requests = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/identityGovernance/entitlementManagement/assignmentRequests?`$expand=accessPackage&`$filter=accessPackage/id eq '$($accessPackage.Id)'" -OutputType PSObject -DisableCache -ThrowOnFailure foreach ($request in @($requests) | Where-Object { Test-OpenRequest $_ }) { if ($request.status -in $cancellableStatuses -or $request.state -in @("pendingApproval", "scheduled")) { Write-Verbose "$logPrefix Canceling open $($request.requestType) request $($request.id) ($($request.status)) in $($accessPackage.displayName)" try { Invoke-EntraOpsMsGraphQuery -Method POST -Uri "/v1.0/identityGovernance/entitlementManagement/assignmentRequests/$($request.id)/cancel" -ThrowOnFailure | Out-Null } catch { Write-Warning "$logPrefix Failed to cancel request $($request.id) in $($accessPackage.displayName): $($_.Exception.Message)" } } else { Write-Verbose "$logPrefix Waiting for open $($request.requestType) request $($request.id) ($($request.status)) in $($accessPackage.displayName)" } $openRequestIds.Add($request.id) } } if ($openRequestIds.Count -gt 0) { $openResult = Wait-OpenRequest -RequestIds $openRequestIds -Activity "Open assignment requests" $stillOpen = @($openResult.Pending) if ($stillOpen.Count -gt 0) { $details = ($stillOpen | ForEach-Object { "$($_.id) (state '$($_.state)', status '$($_.status)')" }) -join ', ' $failures.Add("$($stillOpen.Count) open assignment request(s) did not complete: $details") } } Stop-IncompleteRemoval Write-Progress -Id $progressId -Activity $progressActivity -Status 'Submitting assignment removals' # Step 2: Submit ALL adminRemove requests across all packages at once. $removeRequests = [System.Collections.Generic.List[object]]::new() foreach ($accessPackage in $catalog.accessPackages) { # Filtering by navigation path 'accessPackage/id' requires ConsistencyLevel:eventual + $count=true. $assignments = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/identityGovernance/entitlementManagement/assignments?`$count=true&`$filter=accessPackage/id eq '$($accessPackage.Id)'&`$expand=target" -OutputType PSObject -DisableCache -ConsistencyLevel "eventual" -ThrowOnFailure foreach ($assignment in @($assignments) | Where-Object { $_ -and ($_.state -ine "expired") }) { $targetEmail = if ($assignment.target.PSObject.Properties.Name -contains 'email') { " [$($assignment.target.email)]" } $description = "$($assignment.target.displayName)$targetEmail in $($accessPackage.displayName)" Write-Verbose "$logPrefix Queuing adminRemove for $description" $params = @{ requestType = "adminRemove"; assignment = @{id = $assignment.id } } try { $req = Invoke-EntraOpsMsGraphQuery -Method POST -Uri "/v1.0/identityGovernance/entitlementManagement/assignmentRequests" -Body ($params | ConvertTo-Json -Depth 10) -OutputType PSObject -ThrowOnFailure if (-not $req -or -not $req.id) { throw "Graph returned no assignment removal request ID." } $removeRequests.Add([pscustomobject]@{ Id = $req.id AccessPackageId = $accessPackage.Id Description = $description }) } catch { $failures.Add("adminRemove of assignment $($assignment.id) ($($assignment.target.displayName)) in $($accessPackage.displayName) failed: $($_.Exception.Message)") } } } Write-Progress -Id $progressId -Activity $progressActivity -Status 'Waiting for assignment removals' # Step 3: A terminal request may have failed; only successful removals permit teardown. if ($removeRequests.Count -gt 0) { Write-Verbose "$logPrefix Waiting for $($removeRequests.Count) assignment removal request(s) to complete" $result = Wait-OpenRequest -RequestIds @($removeRequests | ForEach-Object { $_.Id }) -Activity "Assignment removals" foreach ($request in $result.Requests) { $removeRequest = $removeRequests | Where-Object { $_.Id -eq $request.id } | Select-Object -First 1 $state = if ($request.state) { $request.state } else { $request.status } if (Test-OpenRequest $request) { $failures.Add("removal of $($removeRequest.Description) is still in progress after the wait budget (request $($request.id), state '$($request.state)', status '$($request.status)')") } elseif ($state -notin @('delivered', 'fulfilled')) { $failures.Add("removal of $($removeRequest.Description) did not succeed (request $($request.id), state '$($request.state)', status '$($request.status)')") } } } Stop-IncompleteRemoval Write-Progress -Id $progressId -Activity $progressActivity -Status 'Verifying assignments were removed' # Confirm the assignment index has caught up before changing any package resource roles. $check = @{ Remaining = @() } $assignmentsRemoved = Wait-EntraOpsServiceEMCondition -Activity "Absence of active access package assignments" -logPrefix $logPrefix -ProgressStatus { "$($check.Remaining.Count) assignment(s) still visible" } -Condition { $check.Remaining = @(foreach ($accessPackage in $catalog.accessPackages) { $assignments = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/identityGovernance/entitlementManagement/assignments?`$count=true&`$filter=accessPackage/id eq '$($accessPackage.Id)'&`$expand=target" -OutputType PSObject -DisableCache -ConsistencyLevel "eventual" -ThrowOnFailure foreach ($assignment in @($assignments) | Where-Object { $_ -and $_.state -ine 'expired' }) { "$($assignment.id) in $($accessPackage.displayName) (state '$($assignment.state)')" } }) if ($check.Remaining.Count -gt 0) { Write-Verbose "$logPrefix $($check.Remaining.Count) assignment(s) still present: $($check.Remaining -join '; ')" } $check.Remaining.Count -eq 0 } if (-not $assignmentsRemoved) { $failures.Add("assignments still present after removal: $($check.Remaining -join '; ')") } Stop-IncompleteRemoval Write-Progress -Id $progressId -Activity $progressActivity -Status 'Deleting access packages' # Step 4: Remove resource role scopes, then delete packages. Only the known grants-index error is retried. foreach ($accessPackage in $catalog.accessPackages) { try { $resourceRoleScopes = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/identityGovernance/entitlementManagement/accessPackages/$($accessPackage.Id)/resourceRoleScopes" -OutputType PSObject -DisableCache -ThrowOnFailure foreach ($rrs in @($resourceRoleScopes) | Where-Object { $_ -and $_.Id }) { Write-Verbose "$logPrefix Removing resource role scope $($rrs.Id) from $($accessPackage.displayName)" Invoke-EntraOpsMsGraphQuery -Method DELETE -Uri "/v1.0/identityGovernance/entitlementManagement/accessPackages/$($accessPackage.Id)/resourceRoleScopes/$($rrs.Id)" -ThrowOnFailure | Out-Null } $deleteCheck = @{ LastError = $null } $deleted = Wait-EntraOpsServiceEMCondition -Activity "Deletion of $($accessPackage.displayName)" -MaxWaitSeconds 60 -logPrefix $logPrefix -Condition { try { Invoke-EntraOpsMsGraphQuery -Method DELETE -Uri "/v1.0/identityGovernance/entitlementManagement/accessPackages/$($accessPackage.Id)" -ThrowOnFailure -SuppressBadRequestWarning | Out-Null return $true } catch { if ($_.Exception.Data['StatusCode'] -ne 400 -or $_.Exception.Data['ErrorCode'] -ne 'InvalidDeleteEntitlementHasGrants') { throw } $deleteCheck.LastError = $_.Exception.Message Write-Verbose "$logPrefix Grants index still prevents deletion of $($accessPackage.displayName); retrying within the 60-second budget" return $false } } if (-not $deleted) { throw "Active grants still prevent package deletion after the retry budget: $($deleteCheck.LastError)" } } catch { $failures.Add("deleting access package $($accessPackage.displayName) failed: $($_.Exception.Message)") } } Stop-IncompleteRemoval Write-Progress -Id $progressId -Activity $progressActivity -Status 'Deleting service groups' # Step 5: Delete the Entra groups created by the landing zone (identified by their mailNickname). # The catalog is deleted afterwards, so a re-run can still find it when a group deletion fails. $serviceName = $ServiceCatalogName -replace '^Catalog-' Write-Verbose "$logPrefix Deleting associated Entra groups from catalog resources" $deletedGroupIds = [System.Collections.Generic.List[string]]::new() $removalFailures = [System.Collections.Generic.List[string]]::new() foreach ($resource in $catalog.Resources | Where-Object { $_.OriginSystem -eq "AadGroup" }) { if ($resource.OriginId -in $ExcludeGroupIds) { Write-Verbose "$logPrefix Skipping excluded group: $($resource.DisplayName) [$($resource.OriginId)]" continue } try { $group = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/groups/$($resource.OriginId)?`$select=id,displayName,mailNickname" -OutputType PSObject -DisableCache -ThrowOnFailure -SuppressNotFoundWarning } catch { if ($_.Exception.Data['StatusCode'] -ne 404) { $removalFailures.Add("lookup of group $($resource.DisplayName) [$($resource.OriginId)] failed: $($_.Exception.Message)") continue } $group = $null } if (-not $group) { Write-Verbose "$logPrefix Group $($resource.DisplayName) [$($resource.OriginId)] not found, skipping" continue } if ($group.MailNickname -notlike "$serviceName.*") { Write-Warning "$logPrefix Keeping group $($resource.DisplayName) [$($resource.OriginId)]: it wasn't created by this landing zone (mailNickname '$($group.MailNickname)')" continue } try { Write-Verbose "$logPrefix Deleting group: $($resource.DisplayName) [$($resource.OriginId)]" Invoke-EntraOpsMsGraphQuery -Method DELETE -Uri "/v1.0/groups/$($resource.OriginId)" -ThrowOnFailure | Out-Null $deletedGroupIds.Add($resource.OriginId) } catch { Write-Verbose "$logPrefix Failed to delete group $($resource.DisplayName) [$($resource.OriginId)]" $removalFailures.Add("deleting group $($resource.DisplayName) [$($resource.OriginId)] failed: $($_.Exception.Message)") } } if ($deletedGroupIds.Count -gt 0 -and (Get-EntraOpsServiceEMConfigValue -Path 'UpdateObjectClassificationFile') -eq $true) { Update-EntraOpsServiceEMObjectClassificationFile -RemoveObjectIds $deletedGroupIds -logPrefix $logPrefix | Out-Null } Write-Progress -Id $progressId -Activity $progressActivity -Status 'Deleting the catalog' # Step 6: Delete the catalog, unless a group is left that a re-run has to find through it. if ($removalFailures.Count -gt 0) { Write-Warning "$logPrefix Keeping catalog '$ServiceCatalogName': $($removalFailures.Count) group(s) could not be removed" } else { try { Write-Verbose "$logPrefix Deleting Catalog" Invoke-EntraOpsMsGraphQuery -Method DELETE -Uri "/v1.0/identityGovernance/entitlementManagement/catalogs/$($catalog.Id)" -ThrowOnFailure | Out-Null } catch { $removalFailures.Add("deleting catalog $ServiceCatalogName [$($catalog.Id)] failed: $($_.Exception.Message)") } } Write-Progress -Id $progressId -Activity $progressActivity -Status 'Processing Azure resource cleanup' # Step 7: Delete Azure resource group (opt-in). # Only the Rg scope creates a resource group, named without its "Rg-" prefix (see New-EntraOpsServiceAZContainer) $rgName = "RG-$($serviceName -replace '^Rg-')" if ($serviceName -like 'Sub-*') { $azureSummary = 'subscription role cleanup requires manual review' Write-Warning "$logPrefix '$ServiceCatalogName' is a subscription scope: no resource group is deleted. Remove Azure role assignments and PIM eligible assignments of the deleted groups on the subscription manually." } elseif ($RemoveAzureResourceGroup) { $previousAzContext = Get-AzContext try { if ($previousAzContext.Subscription.Id -ne $SubscriptionId) { Write-Verbose "$logPrefix Switching Azure context to subscription $SubscriptionId" Set-AzContext -Subscription $SubscriptionId -Tenant $previousAzContext.Tenant.Id -ErrorAction Stop | Out-Null } Write-Verbose "$logPrefix Looking up Azure Resource Group: $rgName" $rg = Get-AzResourceGroup -Name $rgName -ErrorAction Stop if ($rg -and $rg.Tags.EntraOpsServiceEM -ne $serviceName) { Write-Warning "$logPrefix Keeping Azure Resource Group '$rgName': it isn't tagged EntraOpsServiceEM=$serviceName, so it wasn't created by this landing zone. Delete it manually if needed." } elseif ($rg) { # PIM eligible schedules outlive the resource group and would block a later landing zone with the same name try { $schedules = @(Get-AzRoleEligibilitySchedule -Scope $rg.ResourceId -ErrorAction Stop | Where-Object { $_.Scope -ieq $rg.ResourceId }) foreach ($schedule in $schedules) { Write-Verbose "$logPrefix Removing eligible assignment $($schedule.RoleDefinitionDisplayName) of $($schedule.PrincipalId) on $rgName" New-AzRoleEligibilityScheduleRequest -Name ([guid]::NewGuid()) -Scope $rg.ResourceId -RoleDefinitionId $schedule.RoleDefinitionId -PrincipalId $schedule.PrincipalId -RequestType AdminRemove -ErrorAction Stop | Out-Null } } catch { $azureCleanupNeedsAttention = $true Write-Warning "$logPrefix Failed to remove PIM eligible assignments on '$rgName': $($_.Exception.Message). They may remain as leftovers." } Write-Verbose "$logPrefix Deleting Azure Resource Group: $rgName" Remove-AzResourceGroup -Name $rgName -Force -ErrorAction Stop | Out-Null $azureSummary = "Azure resource group '$rgName' deleted" Write-Verbose "$logPrefix Azure Resource Group deleted: $rgName" } } catch { if ($_.Exception.Message -like "*not exist*" -or $_.Exception.Message -like "*ResourceGroupNotFound*") { $azureSummary = "Azure resource group '$rgName' not found" Write-Verbose "$logPrefix Azure Resource Group not found: $rgName — skipping" } else { $azureCleanupNeedsAttention = $true Write-Verbose "$logPrefix Failed to delete Azure Resource Group: $rgName" Write-Error $_ } } finally { if ($previousAzContext -and (Get-AzContext).Subscription.Id -ne $previousAzContext.Subscription.Id) { Write-Verbose "$logPrefix Restoring Azure context to subscription $($previousAzContext.Subscription.Id)" Set-AzContext -Context $previousAzContext -ErrorAction SilentlyContinue | Out-Null } } } else { Write-Warning "$logPrefix Azure Resource Group '$rgName' is kept (use -RemoveAzureResourceGroup to delete it). Role assignments of the deleted groups on it remain as orphaned assignments." } if ($removalFailures.Count -gt 0) { throw "$logPrefix Removal of '$ServiceCatalogName' is incomplete; resolve the following and run the cmdlet again: $($removalFailures -join '; ')" } if ($azureCleanupNeedsAttention) { Write-Host "$logPrefix Catalog '$ServiceCatalogName' removed; $($deletedGroupIds.Count) service group(s) deleted; Azure cleanup needs attention. See the warnings and errors above." } else { Write-Host "$logPrefix Catalog '$ServiceCatalogName' removed; $($deletedGroupIds.Count) service group(s) deleted; $azureSummary." } } end { return @{} } clean { if ($progressActivity) { Write-Progress -Id $progressId -Activity $progressActivity -Completed } } } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDR7KqoYtLAAMj4 # ShjyC3h4U7BOFRzX7VfloQ8PtlJL9KCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # 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 # ARUwLwYJKoZIhvcNAQkEMSIEIBspRC75Hv9r15bJQHE6fnV/mjNH7veQBXLy/Sfg # UvQuMA0GCSqGSIb3DQEBAQUABIIBgBIjuYUIGR4uuX+yqqO+im1OFNT3kgoNrbqg # JMpXVrofFuBXDb7TzU94jpPTYN9mZ8Y32kajZNU9SkHHWGVJDVVhXR+bfIP7D4Vv # FVS6uSIVuVaQ5u0rrvO5bPU757wavUKgRi3+OHbIBWKc82EfAFOk7zkLj2/+Ac0F # EUz7T0BNE8DN7y9+w5KNprSS2pclQaDaIpnMlTzreCW2imRoR6aAEDqtBqXDqAjc # P3q8nNfL/B9vnT8aP5OI0NEzcA4YV402CKYMJt8gye45W4CYqvS5oDaegEfVmquu # ro2v/6D/8ptv711v/pbXkmCVrXhK4YNp0eDKLfTcvsu7D18MaYUdTle9AjwxvQKK # bwHBZNhfDdelvJKdCzVc5bfS0w9fheAoAKqe3OCu6/wZPyQxXz+Hiriw9xYe2owT # Pv/ud4CxsZFYzeInGIJngw6cQd7F448mOpFYT0HxsNIBBcIwe5cPwXM8jOhde1/m # Fhwt8Vc9CyyOQJcruNmZ8oG41VdapKGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMjcwNTQxMThaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDDooYkeg+NBVn5Q2thZhs6OIHES9aQ9welG # 5Z8tLQ9/jN9EjX4sgM9XQtK6td8W3h4wgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIAflx83HugoQ1a0urpuA66Dx/j7/IfrBnhd6GyCCbJXqJhASDp # P82ause3ddwfWP267TnVYHgAoPdlElaXH6EfI4XhI7D8C/GQAWSJ2cFfY3u7ay6y # +zlCeZ6pu9QwgYG17FSOEQ1txzLmsRFX1MOIbmnG/0iF82IXNh3QMx2AX6LZ2wMM # xk0EG0d9W5hiDf9VUhDF39X6WI+C9kbW+sTIux8hdUDq0Jsd2zZuAlIb5PLSsW3L # ekk+Lj7tbdZ5WcnlLez4qAu844xF82uIGClm/IH0muETRD14qaa3SocGwk4PM2nP # 0lS7OoQgiJUTqonQaC8Je7EmtFoyCMlK5s24YldbNTJaYJ7f9jinVmm3OXxERbjE # Yy7hMll/lrx9qTupwB0GOxe+9UzyPz1QcJuTR0VF/O11YvNcpkxPhbPH1qg9/U0v # FYAUeaDu+jfOHxTyOL3DPS9DnzAXe6ObXH4HlF+giw2aWrxFNxx2uMnD/RGoAgWC # TYW97yA+cux6glGQS5WPzJxiuwzFsRySt+D843dKySOzsTzPIFwIrBvZiqXBlknG # aPF0cPNiV5vr3IDpF5OojeSieytCvYUKKW78Kk0dltfDSo8JKyDUfoCLEPevUieM # gDJ0UZecoKu4dw+y7zwuK913J8yiY3HlM4wsIQ40d7t+bt/FT9pnERAbShc= # SIG # End signature block |