Public/ContainerdTools.psm1
########################################################################### # # # Copyright (c) Microsoft Corporation. All rights reserved. # # # # This code is licensed under the MIT License (MIT). # # # ########################################################################### using module "..\Private\CommonToolUtilities.psm1" $ModuleParentPath = Split-Path -Parent $PSScriptRoot Import-Module -Name "$ModuleParentPath\Private\CommonToolUtilities.psm1" -Force function Get-ContainerdLatestVersion { $latestVersion = Get-LatestToolVersion -Tool "containerd" return $latestVersion } function Install-Containerd { [CmdletBinding( SupportsShouldProcess, ConfirmImpact = 'High' )] param( [parameter(HelpMessage = "ContainerD version to use. Defaults to 'latest'")] [string]$Version = "latest", [parameter(HelpMessage = "Path to install containerd. Defaults to ~\program files\containerd")] [string]$InstallPath = "$Env:ProgramFiles\Containerd", [parameter(HelpMessage = "Path to download files. Defaults to user's Downloads folder")] [string]$DownloadPath = "$HOME\Downloads", [Parameter(HelpMessage = "Register and start Containerd Service")] [switch]$Setup, [Parameter(HelpMessage = 'OS architecture to download files for. Default is $env:PROCESSOR_ARCHITECTURE')] [ValidateSet('amd64', '386', "arm", "arm64")] [string]$OSArchitecture = $env:PROCESSOR_ARCHITECTURE, [Switch] [parameter(HelpMessage = "Installs Containerd even if the tool already exists at the specified path")] $Force ) begin { # Check if Containerd is alread installed $isInstalled = -not (Test-EmptyDirectory -Path $InstallPath) $WhatIfMessage = "Containerd will be installed at $InstallPath" if ($isInstalled) { $WhatIfMessage = "Containerd will be uninstalled from and reinstalled at $InstallPath" } if ($Setup) { <# Action when this condition is true #> $WhatIfMessage = "Containerd will be installed at $InstallPath and containerd service will be registered and started" } } process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) { # Check if tool already exists at specified location if ($isInstalled) { $errMsg = "Containerd already exists at $InstallPath or the directory is not empty" Write-Warning $errMsg # Uninstall if tool exists at specified location. Requires user consent try { Uninstall-Containerd -Path "$InstallPath" -Confirm:$false -Force:$Force | Out-Null } catch { Throw "Containerd installation failed. $_" } } # Get Containerd version to install if (!$Version) { # Get default version $Version = Get-ContainerdLatestVersion } $Version = $Version.TrimStart('v') Write-Output "Downloading and installing Containerd v$version at $InstallPath" # Download files $downloadParams = @{ ToolName = "Containerd" Repository = "$CONTAINERD_REPO" Version = $version OSArchitecture = $OSArchitecture DownloadPath = $DownloadPath ChecksumSchemaFile = $null # QUESTION: Do we need them all? Containerd release contains multiple files. containerd, cri-containerd, cri-containerd-cni # Matches eg: containerd-1.7.21-windows-amd64.tar.gz and containerd-1.7.21-windows-amd64.tar.gz.sha256sum FileFilterRegEx = "(?:^containerd-<__VERSION__>-windows-$OSArchitecture.*.tar.gz(.*)?)$" } $downloadParamsProperties = [FileDownloadParameters]::new( $downloadParams.ToolName, $downloadParams.Repository, $downloadParams.Version, $downloadParams.OSArchitecture, $downloadParams.DownloadPath, $downloadParams.ChecksumSchemaFile, $downloadParams.FileFilterRegEx ) $sourceFile = Get-InstallationFile -FileParameters $downloadParamsProperties # Untar and install tool $params = @{ Feature = "containerd" InstallPath = $InstallPath SourceFile = $sourceFile EnvPath = "$InstallPath\bin" cleanup = $true } Install-RequiredFeature @params Write-Output "Containerd v$version successfully installed at $InstallPath `n" $showCommands = $true try { if ($Setup) { Register-ContainerdService -ContainerdPath $InstallPath -Start -Force:$true $showCommands = $false } } catch { Write-Warning "Failed to setup Containerd service. $_" } if ($showCommands) { $commands = (Get-command -Name '*containerd*' | Where-Object { $_.Source -like 'Containers-Toolkit' -and $_.Name -ne 'Install-Containerd' }).Name $message = "Other useful Containerd commands: $($commands -join ', ').`nTo learn more about each command, run Get-Help <command-name>, e.g., 'Get-Help Register-ContainerdService'" Write-Information -MessageData $message -Tags "Instructions" -InformationAction Continue } Write-Output "For containerd usage: run 'containerd -h'`n" } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function / script return } } } # YAGNI: Is this necessary? function Build-ContainerdFromSource { Throw "Method or operation not implemented." } function Start-ContainerdService { [CmdletBinding( SupportsShouldProcess = $true )] param() process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Starts containerd service")) { Invoke-ServiceAction -Service 'Containerd' -Action 'Start' } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function / script return } } } function Stop-ContainerdService { [CmdletBinding( SupportsShouldProcess = $true )] param() process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Stop containerd service")) { Invoke-ServiceAction -Service 'Containerd' -Action 'Stop' } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function / script return } } } function Register-ContainerdService { [CmdletBinding( SupportsShouldProcess = $true )] param( [parameter(HelpMessage = "Containerd path")] [String]$ContainerdPath, [parameter(HelpMessage = "Specify to start Containerd service after registration is complete")] [Switch]$Start, [parameter(HelpMessage = "Bypass confirmation to register containerd service")] [Switch]$Force ) begin { if (!$ContainerdPath) { $ContainerdPath = Get-DefaultInstallPath -Tool "containerd" } $containerdExecutable = "$ContainerdPath\bin\containerd.exe" $WhatIfMessage = 'Registers containerd service.' if ($Start) { $WhatIfMessage = "Registers and starts containerd service." } } process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) { if (Test-EmptyDirectory -Path $ContainerdPath) { Throw "Containerd does not exist at $ContainerdPath or the directory is empty" } # Check containerd service is already registered if (Test-ServiceRegistered -Service 'containerd') { Write-Warning ( -join @("Containerd service already registered. To re-register the service, " "stop the service by running 'Stop-Service containerd' or 'Stop-ContainerdService', then " "run 'containerd --unregister-service'. Wait for containerd service to be deregistered, " "then re-reun this command.")) return } $consent = $force if (!$ENV:PESTER) { $consent = $force -or $PSCmdlet.ShouldContinue('', "Are you sure you want to register containerd service?") } if (!$consent) { Write-Error "containerd service registration cancelled." return } Write-Output "Configuring containerd service" Add-MpPreference -ExclusionProcess $containerdExecutable # Get default containerd config and write to file $containerdConfigFile = "$ContainerdPath\config.toml" Write-Debug "Containerd config file: $containerdConfigFile" $output = Invoke-ExecutableCommand -Executable $containerdExecutable -Arguments "config default" $output.StandardOutput.ReadToEnd() | Out-File -FilePath $containerdConfigFile -Encoding ascii -Force # Check config file is not empty $isEmptyConfig = Test-ConfFileEmpty -Path "$containerdConfigFile" if ($isEmptyConfig) { Throw "Config file is empty. '$containerdConfigFile'" } Write-Output "Review containerd configutations at $containerdConfigFile" # Register containerd service $output = Invoke-ExecutableCommand -Executable $containerdExecutable -Arguments "--register-service --log-level debug --service-name containerd --log-file `"$env:TEMP\containerd.log`"" if ($output.ExitCode -ne 0) { Throw "Failed to register containerd service. $($output.StandardError.ReadToEnd())" } $containerdService = Get-Service -Name containerd -ErrorAction SilentlyContinue if ($null -eq $containerdService ) { Throw "Failed to register containerd service. $($Error[0].Exception.Message)" } Set-Service containerd -StartupType Automatic Write-Output "Successfully registered Containerd service." if ($Start) { Start-ContainerdService Write-Output "Successfully started Containerd service." } else { Write-Information -InformationAction Continue -MessageData "To start containerd service, run 'Start-Service containerd' or 'Start-ContainerdService'" } Write-Debug $(Get-Service 'containerd' -ErrorAction SilentlyContinue | Format-Table -AutoSize | Out-String) } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function / script return } } } function Uninstall-Containerd { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'High' )] param( [parameter(HelpMessage = "Containerd path")] [String]$Path, [parameter(HelpMessage = "Bypass confirmation to uninstall Containerd")] [Switch] $Force ) begin { $tool = 'Containerd' if (!$Path) { $Path = Get-DefaultInstallPath -Tool $tool } $WhatIfMessage = "Containerd will be uninstalled from $path and containerd service will be stopped and unregistered" } process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) { if (Test-EmptyDirectory -Path $path) { Write-Output "$tool does not exist at $Path or the directory is empty" return } $consent = $force if (!$ENV:PESTER) { $consent = $force -or $PSCmdlet.ShouldContinue($Path, 'Are you sure you want to uninstall Containerd?') } if (!$consent) { Throw "$tool uninstallation cancelled." } Write-Warning "Uninstalling preinstalled $tool at the path $path" try { Uninstall-ContainerdHelper -Path $path } catch { Throw "Could not uninstall $tool. $_" } } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function / script return } } } function Uninstall-ContainerdHelper { param( [ValidateNotNullOrEmpty()] [parameter(Mandatory = $true, HelpMessage = "Containerd path")] [String]$Path ) if (Test-EmptyDirectory -Path $Path) { Write-Error "Containerd does not exist at $Path or the directory is empty." return } try { if (Test-ServiceRegistered -Service 'containerd') { Stop-ContainerdService Unregister-Containerd -ContainerdPath $Path } } catch { Throw "Could not stop or unregister containerd service. $_" } # Delete the containerd key Remove-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Services\containerd" -Recurse -Force -ErrorAction Ignore # Remove the folder where containerd is installed and related folders Remove-Item -Path $Path -Recurse -Force # Delete containerd programdata Uninstall-ProgramFiles "$ENV:ProgramData\Containerd" # Remove from env path Remove-FeatureFromPath -Feature "containerd" Write-Output "Successfully uninstalled Containerd." } function Unregister-Containerd ($containerdPath) { if (!(Test-ServiceRegistered -Service 'Containerd')) { Write-Warning "Containerd service does not exist as an installed service." return } # Unregister containerd service $containerdExecutable = "$ContainerdPath\bin\containerd.exe" $output = Invoke-ExecutableCommand -Executable $containerdExecutable -Arguments "--unregister-service" if ($output.ExitCode -ne 0) { Throw "Could not unregister containerd service. $($output.StandardError.ReadToEnd())" } else { # Wait for service to be unregistered # Failure to wait causes "The specified service has been marked for deletion." error Start-Sleep -Seconds 15 } } Export-ModuleMember -Function Get-ContainerdLatestVersion Export-ModuleMember -Function Install-Containerd Export-ModuleMember -Function Start-ContainerdService -Alias Start-Containerd Export-ModuleMember -Function Stop-ContainerdService -Alias Stop-Containerd Export-ModuleMember -Function Register-ContainerdService Export-ModuleMember -Function Uninstall-Containerd, Uninstall-ContainerdHelper # SIG # Begin signature block # MIIoUwYJKoZIhvcNAQcCoIIoRDCCKEACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCsY9/dRZnhgtF6 # 95DeapV/AA5zdbR3im9cSxHs+oZVrqCCDZowggYYMIIEAKADAgECAhMzAAAD87lq # ZLvv9+1jAAAAAAPzMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwNzE3MjEwMjM0WhcNMjUwOTE1MjEwMjM0WjCBiDEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWlj # cm9zb2Z0IDNyZCBQYXJ0eSBBcHBsaWNhdGlvbiBDb21wb25lbnQwggEiMA0GCSqG # SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4lnJhNSR/+M4vb7CMAivTMYSYEi0EnHXS # SDat/9BwPD2i0F1hj7W80W8dEulv51fz1NAILKbeIp2JaS777mffYHfxMUw/BcQs # yzW6J1e5xUPNguGU2GFXrc5s+jyK39AIpE6ATZecjMDuAu4P1nVEcZKDR0fBbP7Q # 8CFe13PgnZi3n/4UXbwaElvFkrwia4wh88gJ+7lDFkZUOI1H1xxENgP9y2LlCifr # HOahAXcY+TaSyeZqv+E0jV5SkBq8zNFCeWyMbooJPPsSG+LQEfS2sO4xNrNHZMlJ # iziV79h12qxLicPeswugNdD+aBXqF0fwoovPuvSwaKT/wM8okNMxAgMBAAGjggGC # MIIBfjAfBgNVHSUEGDAWBgorBgEEAYI3TBEBBggrBgEFBQcDAzAdBgNVHQ4EFgQU # 1Rdsgw/dRz6oCuO8sKNsrodmEw8wVAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1p # Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMx # NTIyKzUwMjUyMTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNV # HR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny # bC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUw # UzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j # ZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQC # MAAwDQYJKoZIhvcNAQELBQADggIBAGNFKGiwTGNU85l+83tJhB6N2BPvaT9TAkuE # 7uUniu1VU6b1/erckmXiuVqBLg2sGzE7O122bK871fNWpiEr9/pMflah1PL/+0D6 # XCodWS41UmWILRUhSqKURZ+iceq5n4EUiKcapBKhZ6uCKXSX62CVHlvNv4N6JA7N # UztE0h3jAEQeLoGSFlfNqvkTOobE5NwlMRSJWhQ6SYnswiTY1QU06pSTaP/eVedL # Rk6q3t/U9ibvVwHT+eTw1ASBbZoZOxWA3FjrK9XisOT/GoyV2w9D3gzKFGOElCFp # RvceGeRKy2N3YSRjjgV9VYlUM5G/cz4ZigACtnd4jfE7ebbKmiDv2UB1IUpxSzS4 # TAO1bkANbwJ/frfSikUPGfZS2fRRXMWZTeQL5bTBcmW7INNTIxM2MxF6QqlLtmkJ # Z3iv9cocwQ1MzWrrd1BU/ipLwgGtbw7mCestXZjP2amFeQjucAtKt9PkENnWfUex # rwQZnZJWF3mCVvJTh6ACU0CJyO0a2a+RiuxsvR8QpMw1gE3744SbM75nABu84/zd # kxfRkiXro5IsUJ+eNVXVKElvFngBzpbYtTVf1Pdcs8y0sEo6FxGQkzwKNLHe28tG # 1H3NTHUNa0LQUKRjMq/58Iav9qq4rW1QIzfXAEnBDSBFv1E4vLmx2LghqMRPyTAB # ZurrCCn1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCB # iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp # TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEw # NzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5n # IFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAc # Lq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDI # OdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXp # ZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t # 00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD # 2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVO # VpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWY # OUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0P # UUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF # 78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhf # si+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCV # mj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQB # gjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEE # AYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB # /zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+g # TaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9N # aWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBO # BggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9N # aWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEG # CSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAd # AEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAd # MA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5 # DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzS # Gksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9Msm # AkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXv # biWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ # 2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQamASooPoI/E01mC8Cz # TfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNb # B5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85el # CUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4 # GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFci # oXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMT # DXpQzTGCGg8wghoLAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIw # MTECEzMAAAPzuWpku+/37WMAAAAAA/MwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZI # hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC # ARUwLwYJKoZIhvcNAQkEMSIEIGNehm1CCBthbhtWgZlwZ5tpNSYlNvSiFjBtdyA0 # lznsMEQGCisGAQQBgjcCAQwxNjA0oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpo # dHRwczovL3d3dy5taWNyb3NvZnQuY29tIDANBgkqhkiG9w0BAQEFAASCAQBA4F1+ # teAeyimWQxfELpKioaCkBy2LMSNr7FPin10dl4oO/KeDW6AmVBkPVS98IkMmZjUJ # fWduIXQx099lw9fODKzfrJIbMqmXFBhx2mmZ6kx5F4EivdRZpKZI1fQwFEI6Tho4 # sdaiVCNqMB6rl9OSO9DBh4/+NaONYw8wtJSinx9fJVw9jTt7/7xM21+U7P0wyGK1 # EuwbpTh2uujtikCahareWyYqzuT1z9jiM2CxiHM501RKcVovH7dNiim1jVC7AZl8 # 7IjunVfQEnevWNVJ422x0DIxzmsCr/Ui2OANi3rtlDGwA5+yop0dCYGZi3CreZLt # y06cheET9iywFjnroYIXlzCCF5MGCisGAQQBgjcDAwExgheDMIIXfwYJKoZIhvcN # AQcCoIIXcDCCF2wCAQMxDzANBglghkgBZQMEAgEFADCCAVIGCyqGSIb3DQEJEAEE # oIIBQQSCAT0wggE5AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIKQZ # 1MEht/f5rfdKTfwBGovQFhqRiwzjC3SBToraVMQxAgZoGMRdOiMYEzIwMjUwNTE1 # MTYxOTUzLjY1OVowBIACAfSggdGkgc4wgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJh # dGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjpGMDAyLTA1RTAtRDk0NzEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEe0wggcgMIIF # CKADAgECAhMzAAACBTx1bIJEh83+AAEAAAIFMA0GCSqGSIb3DQEBCwUAMHwxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv # c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDEzMDE5NDI0OVoXDTI2MDQy # MjE5NDI0OVowgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # JTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsT # Hm5TaGllbGQgVFNTIEVTTjpGMDAyLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z # b2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC # AgoCggIBAJKS8t93uAXWvbAZ3LzkIVhcdQLzLATIPgtEu/RZgRd55nS7li4runZx # dXNCZk84dM3xNaobZI+VRv7s+V3MUMMCVHe9TymI6OaG72sdbjczZ1uiv2OX2CW6 # HPBR3ZJyJUZrt/23ru0zcoUpFIxcW0coXcrAEHtpWj5vWrLmn0NaWjY3kUasGocw # RWU3LOt4digMsv6bx9Kyoy7+JhSrHMrkXhLshjk16YAHwH4DdCDBUiLrgokh94pl # R6JYoJN/ih1SA/cBCKXGHjw/rPsBPggDR0wS0qFgsWzhb8o0MyxivsqlA8pUJwLk # Ty4Md0p7C5vLN6eRPHLh9/U3eDzKGjk+L0F+NHRXK2uSakN96nwk/BxvgE04hc6j # Wl90dnwS+dHskVkVCKqkxkWU7kIC5Ngfy6Nzk9QeVowAnw0Rr1MUlM5IGsHs9GB6 # H/o0nbG73LE+H+RU30Eayz3cwLpSOmjF4zjHvRvBvCIrxI0cg7wPxyqXtVJ69Rhu # M3g2iAUXCEEKWGh0T/N4Y+rrLqLEPjPrkgdjfPAVBsFVf/D4v9Uc2f8EwazY8Yee # VGM78qTw0ik3iyGQVCoDV8zTx+usNI0Rj1UoO2mxSkAXnVjWhDq0mFYzV3ed4JeH # pv/o35d4c5ELCdjzcr6kUwyGDyKxdBvopGXrmSDxdgF3gnCRsqhVAgMBAAGjggFJ # MIIBRTAdBgNVHQ4EFgQUdHpauzbtvr7IndsRn2jk10vVsvAwHwYDVR0jBBgwFoAU # n6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFt # cCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcw # AoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3Nv # ZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIw # ADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZI # hvcNAQELBQADggIBAA67GTjtvWLvlXzVPHrGjXTE0ivjNnFhV+QXlMWraSd08eDN # XIueyzD8cqQRlEKBlWmQoQnpjiO8bm26AyL5aO3uFQxKKmT5GkHmAVC+HXGYAQvZ # +V6DNNYSyePTCsRKmUjlne+B/Z4ZcCv9FyoaNKmi4dsPYdj1jcXQ6XoVEMJX8cQY # pEfOfYzmtCkUZKNpxPgOSpViZ6b8Cs59K9WiOcoQhb3XhTEa26ElKv2M6jlGpNfs # Yipu283vOFaV36LdfF2F01x+VDP1iGgWpB7EF6eghGAA8C3AfrzFzOv0swLeX0As # Smey87RGIo9cXiXbb859wV99qmGeX9MPCSIl/E7IAx9QXfEj37eLNPVZfYIWzZFo # 4Crd1yiHInD7FEbQTzCQIeqRXnsFtQETMtfwv2UYnUOjFg2mgNfuJDMn1B2TKmLl # +/vvcTcKHwD62jF4WnoWJ9BnIJzwhwgGUfJqToxtPphNVA2BD+HwUX5Lk6o/sIQI # W8gfYq3Y/QaU670LQF9qyGdOOh2a1kVmym1S+KuT/Yc4suMGIyMPAmxAAUDMm7Ph # m1PKiuu8RHXQaX+ZuZWIeqG80AJ9PM+It+MODK+n2zv9se78JqUqZ6IlQsaOH+XA # CPnfX2mCCwYmpuEngVsVz6hTnN99ub+sqNVnyTE0PeKbXRCnjWfa3+qnI/oRMIIH # cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB # iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp # TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw # OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ # Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh # C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx # WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc # UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc # nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo # veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi # YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9 # fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH # GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X # KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE # R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/ # eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3 # FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd # BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE # AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI # MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB # Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud # HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By # b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO # MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy # dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC # AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk # bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng # ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3 # lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC # gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6 # MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU # BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh # VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+ # fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp # NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI # qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCA1AwggI4AgEBMIH5 # oYHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw # IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5u # U2hpZWxkIFRTUyBFU046RjAwMi0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29m # dCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVANWwf2nW0mf6SvAI # y+o6FW9ett60oIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw # DQYJKoZIhvcNAQELBQACBQDr0HENMCIYDzIwMjUwNTE1MTM1NjI5WhgPMjAyNTA1 # MTYxMzU2MjlaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOvQcQ0CAQAwCgIBAAIC # NTsCAf8wBwIBAAICEr0wCgIFAOvRwo0CAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYK # KwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQsF # AAOCAQEATqn652kJESmVOIUwDdRrca/2TEY4bKEPKxHzAzZQ3zZ2MTFjqaalXQut # lnGFCYOhJHgaimEbH5dcOrBNpMucWQ/qQj8uq7mHq3WqOtn2Mbb6C3dbbnphWnVj # KIXIMg1VY/d+NfQijeo3oaWV8WqkWq0TBtybqYu6lg2s9/Kx+lF3t1zF+KQg7c9q # um8ZGLmU1sau4/faGjEiXyEAAQDOqX/F0bfVh4M3HmDvFemk0T8dy3ut6TV3vp4p # hPHADUjP7yURahv1DVDs8F+50Iqo+mJbce5uENj9seoVdLUY1jbehvXyV2dnv3tE # zngGsSmIeDN2eAM+ro9DPC6aSZjm6TGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU # aW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACBTx1bIJEh83+AAEAAAIFMA0GCWCGSAFl # AwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcN # AQkEMSIEIJiGVQdrvYq9I4f5FYmysTvTuDc84zNgTwM8DXu3TXPGMIH6BgsqhkiG # 9w0BCRACLzGB6jCB5zCB5DCBvQQggA0DPdx5jS6aF1YtHawmmrQ4+q0kNMBhGaMd # WTARb0gwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA # AgU8dWyCRIfN/gABAAACBTAiBCDACpw6tCWJAvq6dh5nXkZsLIVTmztOC3UL6pPp # MbJAHzANBgkqhkiG9w0BAQsFAASCAgAPmMJe/aWKkqvJ2O+SVFRzux2VsAbOHuef # r5wR9y0Oem1RJU+ipvWOYTp1lGgikynbd4PhNt898Km+XSjtNP2jIriS5/Scg890 # BnRrk3jNoesN7eCpwCzIjuJop/j6d3RoeEnvgN9groFo8Uci7+Vagg22Bq5KMOr0 # vdmpJWGdQ5fgu+7zPE6laJk6AW6WZPqTCUfpnJTxp4B9Kjj8vMZD3sOoAZNtBQ12 # 2TmrUGAWGCJ5fsv9KVkWmOkAY71jmWtHl9Z3yPdXqL3lL85a7+iSAsp7yZeYNFGP # CxvroepJv70my1tyQ8Qu7wbCTD63DJZRfqLMbxtThmbUkqB5NROATkTgYoOMzSi7 # UidNbFIVEc53esq5zG0y62JqE+69Npv8cC8oiNbdAzGyyfdAJfQQ+f8vQu9SX/tB # te+pUPK014BeFdh6tG0ABkdxdzI9x13MomDH3otPf7G2ZcGRpO16tz8iXNWYD7PB # D6Gtq0kXEZEqjdtxv878X52s6kWY7fr2sVkZnlhHc0+KS49hxiBfObKamFlG1KvM # 8/vB43nRzMvGtnBUMZR00hssUnpVFTgGQIzWaXK3v4J2qp+KqnkDJbYajtIjQ5NJ # ghsQjPfSH/kizTAcQCIMDds8gYjTENstZMHbv67U1YZ1jyIouYNCyxjytl3lzkOx # NUwTwOPO5A== # SIG # End signature block |