Public/Remove-AzureDevOpsAccelerator.ps1
|
function Remove-AzureDevOpsAccelerator { <# .SYNOPSIS Removes Azure DevOps resources created by the Azure Landing Zone accelerator bootstrap modules. .DESCRIPTION The Remove-AzureDevOpsAccelerator function performs cleanup of Azure DevOps resources that were created by the Azure Landing Zone accelerator bootstrap process (https://github.com/Azure/accelerator-bootstrap-modules). This includes projects and optionally agent pools. The function operates in the following sequence: 1. Validates Azure CLI and Azure DevOps extension authentication 2. Prompts for confirmation (unless bypassed or in plan mode) 3. Discovers and deletes projects matching the specified patterns 4. Optionally discovers and deletes agent pools matching the specified patterns CRITICAL WARNING: This is a highly destructive operation that will permanently delete Azure DevOps resources. Use with extreme caution and ensure you have appropriate backups and authorization before executing. .PARAMETER Organization The Azure DevOps organization URL or name. Can be provided as either the full URL (e.g., https://dev.azure.com/my-org) or just the organization name (e.g., my-org). This parameter is required. .PARAMETER ProjectNamePatterns An array of regex patterns to match against project names. Projects matching any of these patterns will be deleted. If empty, no projects will be deleted. Default: Empty array (no projects deleted) .PARAMETER AgentPoolNamePatterns An array of regex patterns to match against agent pool names. Agent pools matching any of these patterns will be deleted. If empty, no agent pools will be deleted. Default: Empty array (no agent pools deleted) .PARAMETER BypassConfirmation A switch parameter that bypasses the interactive confirmation prompts. When specified, the function waits for the duration specified in -BypassConfirmationTimeoutSeconds before proceeding, allowing time to cancel. During this timeout, pressing any key will cancel the operation. WARNING: Use this parameter with extreme caution as it reduces safety checks. Default: $false (confirmation required) .PARAMETER BypassConfirmationTimeoutSeconds The number of seconds to wait before proceeding when -BypassConfirmation is used. During this timeout, pressing any key will cancel the operation. This provides a safety window to prevent accidental deletions. Default: 30 seconds .PARAMETER ThrottleLimit The maximum number of parallel operations to execute simultaneously. This controls the degree of parallelism when processing resources. Higher values may improve performance but increase API throttling risk. Default: 11 .PARAMETER PlanMode A switch parameter that enables "dry run" mode. When specified, the function displays what actions would be taken without actually making any changes. This is useful for validating the scope of operations before executing the actual cleanup. Default: $false (execute actual deletions) .EXAMPLE Remove-AzureDevOpsAccelerator -Organization "my-org" -ProjectNamePatterns @("^alz-.*") -PlanMode Shows what projects matching the pattern "^alz-.*" would be deleted from the "my-org" organization without making any changes. .EXAMPLE Remove-AzureDevOpsAccelerator -Organization "https://dev.azure.com/my-org" -ProjectNamePatterns @("^alz-.*") Deletes all projects matching the pattern "^alz-.*" from the "my-org" organization. .EXAMPLE Remove-AzureDevOpsAccelerator -Organization "my-org" -ProjectNamePatterns @("^alz-.*") -AgentPoolNamePatterns @("^alz-.*") Deletes projects and self-hosted agent pools matching the pattern "^alz-.*" from the "my-org" organization. .EXAMPLE Remove-AzureDevOpsAccelerator -Organization "my-org" -ProjectNamePatterns @("^test-alz$") -BypassConfirmation -BypassConfirmationTimeoutSeconds 10 Deletes the project named exactly "test-alz" with a 10-second confirmation bypass timeout. .NOTES This function requires the Azure CLI with the Azure DevOps extension to be installed and authenticated. Install Azure CLI: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli Install Azure DevOps extension: az extension add --name azure-devops Authenticate: az devops login (supports PAT authentication, az login is not required) Required permissions: - Project Collection Administrator or equivalent permissions to delete projects - Agent Pool Administrator permissions to delete agent pools #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, HelpMessage = "[REQUIRED] The Azure DevOps organization URL or name.")] [Alias("org", "AzureDevOpsOrganization")] [string]$Organization, [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Regex patterns to match project names for deletion.")] [Alias("projects")] [string[]]$ProjectNamePatterns = @(), [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Regex patterns to match agent pool names for deletion.")] [Alias("pools")] [string[]]$AgentPoolNamePatterns = @(), [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Bypass interactive confirmation prompts.")] [switch]$BypassConfirmation, [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Seconds to wait when bypassing confirmation.")] [int]$BypassConfirmationTimeoutSeconds = 30, [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Maximum parallel operations.")] [int]$ThrottleLimit = 11, [Parameter(Mandatory = $false, HelpMessage = "[OPTIONAL] Enable dry run mode - no changes will be made.")] [switch]$PlanMode ) function Get-NormalizedOrganizationUrl { param ( [string]$Organization ) # If it's already a URL, return it if ($Organization -match "^https?://") { return $Organization.TrimEnd('/') } # Otherwise, construct the URL return "https://dev.azure.com/$Organization" } # Main execution starts here if ($PSCmdlet.ShouldProcess("Delete Azure DevOps Resources", "delete")) { Test-Tooling -Checks @("AzureDevOpsCli") $TempLogFileForPlan = "" if($PlanMode) { Write-ToConsoleLog "Plan Mode enabled, no changes will be made. All actions will be logged as what would be performed." -IsWarning $TempLogFileForPlan = (New-TemporaryFile).FullName } $funcWriteToConsoleLog = ${function:Write-ToConsoleLog}.ToString() # Normalize organization URL $organizationUrl = Get-NormalizedOrganizationUrl -Organization $Organization Write-ToConsoleLog "Using Azure DevOps organization: $organizationUrl" # Configure Azure DevOps CLI defaults az devops configure --defaults organization=$organizationUrl 2>&1 | Out-Null if($BypassConfirmation) { Write-ToConsoleLog "Bypass confirmation enabled, proceeding without prompts..." -IsWarning Write-ToConsoleLog "This is a highly destructive operation that will permanently delete Azure DevOps resources!" -IsWarning Write-ToConsoleLog "We are waiting $BypassConfirmationTimeoutSeconds seconds to allow for cancellation. Press any key to cancel..." -IsWarning $keyPressed = $false $secondsRunning = 0 while((-not $keyPressed) -and ($secondsRunning -lt $BypassConfirmationTimeoutSeconds)){ $keyPressed = [Console]::KeyAvailable Write-ToConsoleLog ("Waiting for: $($BypassConfirmationTimeoutSeconds-$secondsRunning) seconds. Press any key to cancel...") -IsWarning -Overwrite Start-Sleep -Seconds 1 $secondsRunning++ } if($keyPressed) { Write-ToConsoleLog "Cancellation key pressed, exiting without making any changes..." -IsError return } } Write-ToConsoleLog "Thanks for providing the inputs, getting started..." -IsSuccess $hasProjectPatterns = $ProjectNamePatterns.Count -gt 0 $hasAgentPoolPatterns = $AgentPoolNamePatterns.Count -gt 0 if(-not $hasProjectPatterns -and -not $hasAgentPoolPatterns) { Write-ToConsoleLog "No patterns provided for projects or agent pools. Nothing to do. Exiting..." -IsError return } # Discover resources to delete $projectsToDelete = @() $agentPoolsToDelete = @() # Discover projects if($hasProjectPatterns) { Write-ToConsoleLog "Discovering projects in organization: $organizationUrl" $allProjects = (az devops project list --org $organizationUrl -o json 2>$null) | ConvertFrom-Json if($null -eq $allProjects -or $null -eq $allProjects.value) { Write-ToConsoleLog "Failed to list projects in organization: $organizationUrl" -IsError return } $projectList = $allProjects.value Write-ToConsoleLog "Found $($projectList.Count) total projects in organization: $organizationUrl" foreach($project in $projectList) { foreach($pattern in $ProjectNamePatterns) { if($project.name -match $pattern) { Write-ToConsoleLog "Project matches pattern '$pattern': $($project.name)" $projectsToDelete += @{ Name = $project.name Id = $project.id } break } } } Write-ToConsoleLog "Found $($projectsToDelete.Count) projects matching patterns for deletion" } # Discover agent pools if($hasAgentPoolPatterns) { Write-ToConsoleLog "Discovering agent pools in organization: $organizationUrl" $allAgentPools = (az pipelines pool list --org $organizationUrl -o json 2>$null) | ConvertFrom-Json if($null -eq $allAgentPools) { Write-ToConsoleLog "Failed to list agent pools in organization: $organizationUrl" -IsWarning $allAgentPools = @() } Write-ToConsoleLog "Found $($allAgentPools.Count) total agent pools in organization: $organizationUrl" foreach($pool in $allAgentPools) { # Skip hosted pools (Microsoft-hosted Azure Pipelines) if($pool.isHosted) { Write-ToConsoleLog "Skipping hosted pool: $($pool.name)" continue } foreach($pattern in $AgentPoolNamePatterns) { if($pool.name -match $pattern) { Write-ToConsoleLog "Agent pool matches pattern '$pattern': $($pool.name)" $agentPoolsToDelete += @{ Name = $pool.name Id = $pool.id } break } } } Write-ToConsoleLog "Found $($agentPoolsToDelete.Count) agent pools matching patterns for deletion" } # Confirm deletion $totalResourcesToDelete = $projectsToDelete.Count + $agentPoolsToDelete.Count if($totalResourcesToDelete -eq 0) { Write-ToConsoleLog "No resources found matching the provided patterns. Nothing to delete." -IsWarning return } if(-not $BypassConfirmation) { Write-ToConsoleLog "The following Azure DevOps resources will be deleted:" if($projectsToDelete.Count -gt 0) { Write-ToConsoleLog "Projects ($($projectsToDelete.Count)):" $projectsToDelete | ForEach-Object { Write-ToConsoleLog " - $($_.Name)" } } if($agentPoolsToDelete.Count -gt 0) { Write-ToConsoleLog "Agent Pools ($($agentPoolsToDelete.Count)):" $agentPoolsToDelete | ForEach-Object { Write-ToConsoleLog " - $($_.Name)" } } if($PlanMode) { Write-ToConsoleLog "Skipping confirmation for plan mode" } else { $continue = Invoke-PromptForConfirmation -message "ALL LISTED AZURE DEVOPS RESOURCES WILL BE PERMANENTLY DELETED" if(-not $continue) { Write-ToConsoleLog "Exiting..." return } } } # Delete projects if($projectsToDelete.Count -gt 0) { Write-ToConsoleLog "Deleting projects..." $projectsToDelete | ForEach-Object -Parallel { $funcWriteToConsoleLog = $using:funcWriteToConsoleLog ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog $TempLogFileForPlan = $using:TempLogFileForPlan $orgUrl = $using:organizationUrl $project = $_ if($using:PlanMode) { Write-ToConsoleLog ` "Would delete project: $($project.Name)", ` "Would run: az devops project delete --id $($project.Id) --org $orgUrl --yes" ` -IsPlan -LogFilePath $TempLogFileForPlan } else { Write-ToConsoleLog "Deleting project: $($project.Name)" $result = az devops project delete --id $project.Id --org $orgUrl --yes 2>&1 if($LASTEXITCODE -ne 0) { Write-ToConsoleLog "Failed to delete project: $($project.Name)", "Full error: $result" -IsWarning } else { Write-ToConsoleLog "Deleted project: $($project.Name)" } } } -ThrottleLimit $ThrottleLimit } # Delete agent pools if($agentPoolsToDelete.Count -gt 0) { Write-ToConsoleLog "Deleting agent pools..." $agentPoolsToDelete | ForEach-Object -Parallel { $funcWriteToConsoleLog = $using:funcWriteToConsoleLog ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog $TempLogFileForPlan = $using:TempLogFileForPlan $orgUrl = $using:organizationUrl $pool = $_ if($using:PlanMode) { Write-ToConsoleLog ` "Would delete agent pool: $($pool.Name)", ` "Would run: az devops invoke --org $orgUrl --area distributedtask --resource pools --route-parameters poolId=$($pool.Id) --http-method DELETE --api-version 7.1" ` -IsPlan -LogFilePath $TempLogFileForPlan } else { Write-ToConsoleLog "Deleting agent pool: $($pool.Name)" $result = az devops invoke --org $orgUrl --area distributedtask --resource pools --route-parameters poolId=$($pool.Id) --http-method DELETE --api-version 7.1 2>&1 if($LASTEXITCODE -ne 0) { Write-ToConsoleLog "Failed to delete agent pool: $($pool.Name)", "Full error: $result" -IsWarning } else { Write-ToConsoleLog "Deleted agent pool: $($pool.Name)" } } } -ThrottleLimit $ThrottleLimit } Write-ToConsoleLog "Cleanup completed." -IsSuccess if($PlanMode) { Write-ToConsoleLog "Plan mode enabled, no changes were made." -IsWarning $planLogContents = Get-Content -Path $TempLogFileForPlan -Raw Write-ToConsoleLog @("Plan mode log contents:", $planLogContents) -Color Gray Remove-Item -Path $TempLogFileForPlan -Force } } } # SIG # Begin signature block # MIIoLwYJKoZIhvcNAQcCoIIoIDCCKBwCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA3HNyf3Am68Aym # mTKs/wWFCQsK0HZeEn1JWSMg27M436CCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z # 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy # 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi # 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ # hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ # 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe # UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk # tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj # Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS # DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns # WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO # lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71 # 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9 # nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk # C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm # M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn # lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo # STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGg8wghoLAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB # BQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFrfx+sewb3KkHCHY42LNU6X # ksa0dxHVM4eTbomAnhORMEQGCisGAQQBgjcCAQwxNjA0oBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQuY29tIDANBgkqhkiG9w0B # AQEFAASCAQCZhRTS2vHo5rZ+o71zKx/M9sVUSPOILkknjpGi01wN5494Z2aJOXis # d+xfNPE9qP/RGHEpMgUPLfcCnLKOY9Io/Hi7IMfG15R4ET/KSxZH+BSCa+eJiOI/ # mVsL60X52lW4b/o+Xpto+XkfE8rcJxJGsuNSuYJKS7NH0mijBABsqZ7hrUQSgHFb # 2fxmVT0YQaTdpnnWFFJ52aje8ruE8Aasa3dXFxhMTFZMGs7D/++zFXXxoi6KFzw0 # FCCl5zSyaKmX/t8VKYr/htCaz46C3AAVn9UfIoL9xejB04HLcoWfGQe9HN5uc5XU # sqiuRE0UMP0zLYNg5ijSrl/qQFJW8FlCoYIXlzCCF5MGCisGAQQBgjcDAwExgheD # MIIXfwYJKoZIhvcNAQcCoIIXcDCCF2wCAQMxDzANBglghkgBZQMEAgEFADCCAVIG # CyqGSIb3DQEJEAEEoIIBQQSCAT0wggE5AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZI # AWUDBAIBBQAEIJHVjPccsjfHKy1NnlJbNHmrwtTZVnOm+6ODEASg4zE2AgZpb4/O # JUkYEzIwMjYwMTMwMjAwNjEwLjE5NVowBIACAfSggdGkgc4wgcsxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBB # bWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo4OTAw # LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZaCCEe0wggcgMIIFCKADAgECAhMzAAACDizLKH2VIHVjAAEAAAIOMA0GCSqGSIb3 # DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk # BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDEzMDE5 # NDMwM1oXDTI2MDQyMjE5NDMwM1owgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlv # bnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo4OTAwLTA1RTAtRDk0NzElMCMG # A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBAKzm3uJG1e3SFt6j0wTvxliMijexpC5YwEVDtfiz # 2+ihhEAFM5/amhMdq3H4TCcFQYHVXa38TozCxA2Zjlekz/vloKtl3ECetX2jhO7m # wF6ltt96Gys5ZEEgagkTo+1ah3UKsV6GbV2LPeNjcfyIWuHuep5+eJnVKdtxY8zI # 0jG4YXOlCIMD4TlhLfeZ4yppfCF1vTUKW7KaH/cQq+SePh0ilBkRY48zePFtFUBg # 3kna06tiQlx0PHSXTZX81h3WqS9QGA/Gsq+KrmTPsToBs6J8INIwGByf9ftFDDrf # RCTOqGnSQNTap6L9qj0gea65F5cSOeOmBOyvgBvfcgIAoxjE5B76fnCoRVwT05PK # GZZklLkCdZROeKiTiaDA40FZDUMs4YWRnBdPffgg8Kp3j/f8t38i2LOKy3JRliya # X8LhmF0Atu99jDO/fU7F/w1OZXkgbFZ0eeTYeGHhufNMqiwRoOsm9AyJD6WiiMzt # /luB3IEGdhAGbn7+ImzHDyTbbvMXaNs0j47Czwct5ka3y3q4nZ5WM0PUHRi2CwE/ # RywGWecj7j528thG3RwCrDo+JhLPkVJlxumLTF0Af+N3d3PIYCtvIu6jr0e6B8YQ # Rv+wzTutyg/Wjdxnx5Yxvj4wgHx645vkNU8OcRwWLg0O6Rgz3WDUO3+oh6T6u0Tz # xVLxAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUhXFEaVIRkT7URIrpQYjtg1wQiNsw # HwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKg # UIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAw # XjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j # ZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQw # DAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E # BAMCB4AwDQYJKoZIhvcNAQELBQADggIBAHXgvZMv4vw5cvGcZJqXaHfSZnsWwEWi # iJiRRU5jTkX2mfA9NW58QMZYSk03LY59pQdYg6hD/3+uPA7SFLZKkHQHMwCTaDLP # 3Y0ZY6lZukF0y+utEOmJZmL+4tLhkZ1Gfc/YNxxiaWQ0/69pEBe+e/6anbsqAjv2 # Yn2EbIJBu+0wiORtiguoruwXtZqGf2suNfXLlAkviW8TLdCYD0pEGPnpwS/+UC/M # Ort5KKpGr+kLKrJzy7OZDxJ4pbJa7oONQD2+LzhCyYuOo8YKcfhw/KD633lGlb7v # eyeF7DWIJX7Be7ZHEydaDsSwPl4uQkcuzNQg935cKUP4VO9XTcZ+sMN+T7jl+Uf9 # 4pFlzcxRm2eEsmM/C/cqgoNJxbiJXpJsJHJxg+SqhYGsd/tK8MDsasfZQ63PVZrW # Tbux1mCkbr9z0KoojwJfm+Bpr4DuhgdvhkGPtLy7yyDHBYrseBYNEHI4fcKIm7gs # nyHdOJGRECuYdGnSVs1/WIAq4vzzogoT3Xa/TKrnm3yMzGMFTu6guythUigqTOH6 # wCSCSkY6hkvXj52XFUz3UFq/NriQ4NNSXDNv5KlexKpXHye4HqqFTLumqmDDDWrh # I2EDEWcXGzGJOVqgvvkY3E9HrTmUnZZd6G0SLv/5h8mq8f6+epymoKPJD2E1pXO4 # 4QdfgzK6pyPCMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg # VGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+ # F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU # 88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqY # O7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzp # cGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0Xn # Rm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1 # zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZN # N3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLR # vWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTY # uVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUX # k8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB # 2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKR # PEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0g # BFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5t # aWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQM # MAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQE # AwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQ # W9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNv # bS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBa # BggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqG # SIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOX # PTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6c # qYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/z # jj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz # /AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyR # gNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdU # bZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo # 3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4K # u+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10Cga # iQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9 # vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGC # A1AwggI4AgEBMIH5oYHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMV # AErodj9lYuc5wwRCyOQMCgH8llYIoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh # bXAgUENBIDIwMTAwDQYJKoZIhvcNAQELBQACBQDtJztsMCIYDzIwMjYwMTMwMTQx # NTA4WhgPMjAyNjAxMzExNDE1MDhaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAO0n # O2wCAQAwCgIBAAICCycCAf8wBwIBAAICE1UwCgIFAO0ojOwCAQAwNgYKKwYBBAGE # WQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDAN # BgkqhkiG9w0BAQsFAAOCAQEAhEZuuihnex2TGtOyRlyFC3Bd1BnanC8TrlrP/NzT # bkapxk3DTVE73Ft3CF/U149UehemfCpfCWwJOXQXBedL6c61Ipa5A4LXMk/r8Y9F # VVnLvxEntfmrAkCCXKpr4LF+pVYSDDRr7UYP/AdHjvjr21OIA7xOiMOApkHbRYm0 # FJ+XRB4MhQCUtDJaZ+urLtlGZKGu5ncH21ejTmxqTq9liUsFaZkEJiPcc/7DL/pG # SquODiSb8srxI2HRXZDIkBtcaAyz9J3TuPDtZ8n96jVFchKUwAYwYP+LMZkI7v7/ # Dug0scL+9FK5xhgiO8Xo6paF2OsZLGpAnN0t11LNT9/D9TGCBA0wggQJAgEBMIGT # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACDizLKH2VIHVjAAEA # AAIOMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ # AQQwLwYJKoZIhvcNAQkEMSIEIJne6V7byKygqAVZIfEJOPZnHaIwQbXH1Ola1ptq # xr+zMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgAXQdcyXw6YGQrbrubGhs # pKKHA50/R5Q1dAzKk/NPEoYwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ # Q0EgMjAxMAITMwAAAg4syyh9lSB1YwABAAACDjAiBCCq4TyTWOtfIv3FPamud3CS # s8Epj2AkNI1MZjRpp+osIzANBgkqhkiG9w0BAQsFAASCAgBO8Lk79zgyXZEbQ0wp # SuE4Fzq+LtCHgqkhSV5RAKfsIJCiJv/g7FdoXpJRtkhfURS0ELqFQJwsrsedQn3U # RsetA0JBRO5m6Ies6bFpmRU71o6Hup8Tf8sF5JNyGYd/nSSoh/ERNg8ULuomNTWU # yDBllyHGgPCuGtCnHwQKBhnuJH3YhVMnK/8HkRz2GAuHg/B/N5D3xaFEalVt1E9P # 73FDj359ETRHuzfrI6y6ZkeoYQzq5kF/hnLgMoCP3sKAWJt1SIxE0Rfzrm1IubCB # JZQmpASv6m/2bcyI6fmvwGYjMOiArIr3cB3Bmy8JlBUU/TMKecF3G5+FfFwsLUCm # CgICIb8b6D8y54iSAlgTgZsWlbZY24uLtbCx7JReSOswS0WaH3ZqrkfxKCYtNsU9 # 0IFG2oLMtz9wqe1y6wSOXleA80QT+5uFUvoIM/CStIBpJJ81DqbsnJuxHxzu94gh # NsGTwgNoaTKMJk/2iaIu5LFrwpK+8h2O+jjfDnAJ4jR1TKUfP7sCP/fuCDaupF9y # ylPeLay7iFyFzeNovAK7ilqOX87tUN5u6s13dKGmWsJQMmK/nmc0c8grBQPXY+Y0 # SNOxKa+xlidysqL6973FKTeWQRUP5ALKh491+66OvAk0ENdBmMfKd3QjKtvsRZLi # C9eB3V3Lr7gOdfPJg1WyuphYTg== # SIG # End signature block |