Public/AllToolsUtilities.psm1

###########################################################################
# #
# Copyright (c) Microsoft Corporation. All rights reserved. #
# #
# This code is licensed under the MIT License (MIT). #
# #
###########################################################################


$ModuleParentPath = Split-Path -Parent $PSScriptRoot
Import-Module -Name "$ModuleParentPath\Private\CommonToolUtilities.psm1" -Force


function Show-ContainerTools {
    param (
        [Parameter(HelpMessage = "Show latest release version")]
        [Switch]$Latest,

        [Parameter(HelpMessage = "Tool to show")]
        [ValidateSet("containerd", "buildkit", "nerdctl")]
        [String[]]$ToolName
    )

    $tools = if ($ToolName) { $ToolName } else { @("containerd", "buildkit", "nerdctl") }

    $installedTools = @()
    foreach ($tool in $tools) {
        $installedTools += (Get-InstalledVersion -Feature $tool -Latest:$Latest)
    }

    $registerCommands = (Get-Command -Name "*-*Service" -Module 'Containers-Toolkit').Name -join ', '
    $message = "For unregistered services/daemons, check the tool's help or register the service using `n`t$registerCommands"
    Write-Information -MessageData $message -Tags "Instructions" -InformationAction Continue
    return $installedTools
}

function Install-ContainerTools {
    [CmdletBinding(
        SupportsShouldProcess = $true,
        ConfirmImpact = 'High'
    )]
    param(
        [string]
        [ValidateNotNullOrEmpty()]
        [parameter(HelpMessage = "ContainerD version to use")]
        $ContainerDVersion = "latest",

        [string]
        [ValidateNotNullOrEmpty()]
        [parameter(HelpMessage = "Buildkit version to use")]
        $BuildKitVersion = "latest",

        [string]
        [ValidateNotNullOrEmpty()]
        [parameter(HelpMessage = "nerdctl version to use")]
        $NerdCTLVersion = "latest",

        [String]
        [parameter(HelpMessage = "Path to Install files. Defaults to Program Files")]
        $InstallPath = $Env:ProgramFiles,

        [String]
        [parameter(HelpMessage = "Path to download files. Defaults to user's Downloads folder")]
        $DownloadPath = "$HOME\Downloads",

        [Switch]
        [parameter(HelpMessage = "Force install the tools even if they already exists at the specified path")]
        $Force,

        [switch]
        [parameter(HelpMessage = "Register and Start Containerd and Buildkitd services and set up NAT network")]
        $RegisterServices
    )

    begin {
        # Strip leading "v" from version
        $containerdVersion = $containerdVersion.TrimStart("v")
        $buildKitVersion = $buildKitVersion.TrimStart("v")
        $nerdctlVersion = $nerdctlVersion.TrimStart("v")

        $toInstall = @("containerd v$containerdVersion", "buildkit v$buildKitVersion", "nerdctl v$nerdctlVersion")
        $toInstallString = $($toInstall -join ', ')

        $WhatIfMessage = "$toInstallString will be installed. Any downloaded files will be removed"
        if ($Force) {
            $WhatIfMessage = "$toInstallString will be automatically uninstalled (if they are already installed) and reinstalled. Any downloaded files will be removed"
        }
    }

    process {
        if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) {
            Write-Output "The following tools will be installed: $toInstallString"

            Write-Debug "Downloading files to $DownloadPath"
            Write-Debug "Installing files to $InstallPath"

            $completedInstalls = @()
            $failedInstalls = @()

            $installTasks = @(
                @{
                    name     = "Containerd"
                    function = {
                        Install-Containerd -Force:$force -Confirm:$false `
                            -Version $containerdVersion `
                            -InstallPath "$InstallPath\Containerd" `
                            -DownloadPath "$DownloadPath" `
                            -Setup:$RegisterServices
                    }
                },
                @{
                    name     = "Buildkit"
                    function = {
                        Install-Buildkit -Force:$force -Confirm:$false `
                            -Version $buildKitVersion `
                            -InstallPath "$InstallPath\Buildkit" `
                            -DownloadPath "$DownloadPath" `
                            -Setup:$RegisterServices
                    }
                },
                @{
                    name     = "nerdctl"
                    function = {
                        Install-Nerdctl -Force:$force -Confirm:$false `
                            -Version $nerdctlVersion `
                            -InstallPath "$InstallPath\nerdctl" `
                            -DownloadPath "$DownloadPath"
                    }
                }
            )

            foreach ($task in $installTasks) {
                try {
                    & $task.Function
                    $completedInstalls += $task.Name
                }
                catch {
                    Write-Error "$($task.Name) installation failed. $_"
                    $failedInstalls += $task.Name
                }
            }

            if ($completedInstalls) {
                Write-Output "$($completedInstalls -join ', ') installed successfully.`n"
            }

            if ($failedInstalls) {
                Write-Warning "Installation failed for $($failedInstalls -join ', ')`n"
            }

            if ($RegisterServices) {
                try {
                    Initialize-NatNetwork -Force:$force -Confirm:$false
                }
                catch {
                    Write-Error "Failed to initialize NAT network. $_"
                }
            }
            else {
                $message = @"
To register containerd and buildkitd services and create a NAT network, see help on the following commands:
    Get-Help Register-ContainerdService
    Get-Help Register-BuildkitdService
    Get-Help Initialize-NatNetwork
"@

                Write-Information -MessageData $message -Tags "Instructions" -InformationAction Continue
            }

            Write-Output "Installation complete. See logs for more details"
        }
        else {
            # Code that should be processed if doing a WhatIf operation
            # Must NOT change anything outside of the function / script
            return
        }
    }
}

function Get-InstalledVersion($feature, $Latest) {
    $sourceLocation = $null
    $daemon = $null
    $buildctlPath = $null
    switch ($feature) {
        "buildkit" {
            $blktCommandInfo = Get-Command "build*.exe" | Where-Object { $_.Source -like "*buildkit*" }
            if ($null -ne $blktCommandInfo) {
                # Get buildkitd executable
                $buldkitdCommandInfo = $blktCommandInfo | Where-Object { $_.Name -like "buildkitd.exe" }
                $sourceLocation = $buldkitdCommandInfo.Source
            }
            $daemon = 'buildkitd'

            $buildctlPath = ($blktCommandInfo | Where-Object { $_.Name -like "buildctl.exe" }).Source
        }
        Default {
            $commandInfo = Get-Command "$feature.exe" -ErrorAction Ignore

            if ($null -ne $commandInfo) {
                $sourceLocation = $commandInfo.Source
            }

            if ($feature -eq 'containerd') {
                $daemon = 'containerd'
            }
        }
    }

    $result = [PSCustomObject]@{
        Tool      = $feature
        Installed = $False
    }
    if ($sourceLocation) {
        $result = getToolVersion -Executable $sourceLocation
        Add-Member -InputObject $result -Name 'Tool' -Value $feature -MemberType 'NoteProperty'
        Add-Member -InputObject $result -Name 'Path' -Value $sourceLocation -MemberType 'NoteProperty'
        $result = $result | Select-Object Tool, Path, Installed, Version

        if ($daemon) {
            Add-Member -InputObject $result -Name 'Daemon' -Value $daemon -MemberType 'NoteProperty'
            Add-Member -InputObject $result -Name 'DaemonStatus' -MemberType 'NoteProperty' `
                -Value (getDaemonStatus -Daemon $daemon)
        }

        if ($buildctlPath) {
            $result | Add-Member -Name 'BuildctlPath' -Value $buildctlPath -MemberType 'NoteProperty'
        }
    }

    # Get latest version
    $latestVersion = "-"
    if ($Latest) {
        $latestVersionCommand = "Get-$($feature)LatestVersion"
        $latestVersion = & $latestVersionCommand
        Add-Member -InputObject $result -Name 'LatestVersion' -Value "v$latestVersion" -MemberType 'NoteProperty'
    }

    return $result
}

function getToolVersion($executable) {
    $toolName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetFileName($executable))

    $installedVersion = $null
    try {
        $cmdOutput = Invoke-ExecutableCommand -Executable $executable -Arguments '--version'
        if ($cmdOutput.ExitCode -ne 0) {
            Throw "Couldn't get $toolName version. $($cmdOutput.StandardError.ReadToEnd())"
        }

        $version = $cmdOutput.StandardOutput.ReadToEnd()

        $pattern = "(\d+\.)(\d+\.)(\*|\d+)"
        $installedVersion = ($version | Select-String -Pattern $pattern).Matches.Value
        if ($installedVersion) {
            $installedVersion = "v$installedVersion"
        }
        else {
            $installedVersion = 'unknown'
        }
    }
    catch {
        $installedVersion = "-"
    }

    $Installed = ($null -ne $installedVersion)
    if (!$Installed) {
        $executablePath = Get-Command $executable.Source -ErrorAction Ignore
        $installed = ($null -ne $executablePath)
    }

    $result = [PSCustomObject]@{
        Installed = $Installed
        Version   = $installedVersion
    }
    return $result
}

function getDaemonStatus($daemon) {
    $daemonStatus = Get-Service -Name $daemon -ErrorAction Ignore
    if ($null -eq $daemonStatus) {
        return 'Unregistered'
    }

    return $daemonStatus.Status
}

Export-ModuleMember -Function Show-ContainerTools
Export-ModuleMember -Function Install-ContainerTools

# SIG # Begin signature block
# MIIoWQYJKoZIhvcNAQcCoIIoSjCCKEYCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBtvg+gURWCBzd3
# oEn+qNq/xnt1bdyz8tJa12MD/KP87qCCDYswggYJMIID8aADAgECAhMzAAAD9LjE
# XeFOcLZ+AAAAAAP0MA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjQwNzE3MjEwMjM1WhcNMjUwOTE1MjEwMjM1WjCBiDEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWlj
# cm9zb2Z0IDNyZCBQYXJ0eSBBcHBsaWNhdGlvbiBDb21wb25lbnQwggEiMA0GCSqG
# SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv3P8bL08GKolFW7QNDVOF0aM4iqMxVvAW
# VM124/82xbjAraJkKxieMrQa1Fc95LVGgxmJIi5R6QKMz2MO9bnwC7kSkPqoZJil
# 26bRLY6jinjbwPpK3TzbW7z9bXfWw5bPFlt72NVIdXJ3xtHoYa+AOi++CF2Ry7+7
# o1AzvotJwG6lQSiCMKeMt8apqEF1f+QkDFEUv5tezw9748DeHW9orvo4IPzWa7vW
# QgljB08LKSnzTN9/Jot2coWpFv4YuEoJZmR2ofPJMnDUUruDORTXnxwhfvd/wUmI
# SoEysSqobkNV+qFuUmSShYrx8R1zHm7P6G/iRMIKYmSrIYBKUvndAgMBAAGjggFz
# MIIBbzAfBgNVHSUEGDAWBgorBgEEAYI3TBEBBggrBgEFBQcDAzAdBgNVHQ4EFgQU
# Dz4uMjS8YCSZaU0449GJYQ1ufyowRQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEWMBQGA1UEBRMNMjMxNTIyKzUwMjUxODAfBgNV
# HSMEGDAWgBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNo
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0Ey
# MDExXzIwMTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZF
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQ
# Q0EyMDExXzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEL
# BQADggIBABUCAiEn4g8i5T3VCP8160IY4ERdvZi5QZ2pSnBPW1dswVhLxkNTiCTV
# XKDjTQ4EwDBNSZZGJePz4+t86pKhlBON3S7wswf5fCovJLlIiKbw+E4TZeY6xAxd
# +5zV7Q2lsQhPHxiOY0PIGUE0KJfv/DQUulD8DrE0rru7yOO+DJI0muoK0BbHhRfd
# mAJhp2gbYRkarEIkhML9m3gR12mCBb69Vocm4IyOBivUPMjjvQMkERF7cR07k2uP
# 6dmpR8wtof9la0/K0wgiP5XuQUsAqgzhXrljH7dK7nqGrBDjJtrRdYfvVL+Rcz9i
# YZO280g2uNtac5em3HOEsactAL7XKqZ4o7s9sRyp/bTNLLRmhFMB729IL+Hi0YM7
# C8th3HZ5nP+77L46KUGip6QgRIJs+EO0YNW+AwgMxPfKpTx/Ggh8Z85kP7HLDZJk
# ZdPO/3cgVOTO4ax21vO2yMPCdfoGGr2ZLZw4SjEbGuOZJ22iGMV7tBvHk8nWAt3q
# +j/icAq99GA1nIPnw3jK3K9OwGqwA9eiWsO8/bHMm6s50UKIFupMKm6qObosaVBy
# R58rf8Cxumka7hPy1eSJSzQyA4UqYNTWuChsTfqgRLmLomS6yAu7t4r/bM4mGl+2
# Ki+avhQ4COm3jWWd0V6UGIP3T4zaKNs2GWFBIYsb/6XVvvi7pz/JMIIHejCCBWKg
# AwIBAgIKYQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3Qg
# Q2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYw
# NzA4MjEwOTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGf
# Qhsqa+laUKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRg
# JGyvnkmc6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NE
# t13YxC4Ddato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnn
# Db6gE3e+lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+E
# GvKhL1nkkDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+t
# GSOEy/S6A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxh
# H2rhKEmdX4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AV
# s70b1FVL5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f
# 7Fufr/zdsGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3D
# KI8sj0A3T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9Jaw
# vEagbJjS4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1Ud
# DgQWBBRIbmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi
# AEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRy
# LToCMZBDuRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3Js
# Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDEx
# XzIwMTFfMDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0
# cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDEx
# XzIwMTFfMDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/
# BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2Nz
# L3ByaW1hcnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAA
# bwBsAGkAYwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUA
# A4ICAQBn8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+
# vj/oCso7v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4H
# Limb5j0bpdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6
# aC6VoCo/KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiX
# mE0OPQvyCInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn
# +N4sOiBpmLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAq
# ZaPDXVJihsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5h
# YbXw3MYbBL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/
# RXceNcbSoqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXm
# r/r8i+sLgOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMyk
# XcGhiJtXcVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGiQwghogAgEB
# MIGVMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNV
# BAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAP0uMRd4U5w
# tn4AAAAAA/QwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQB
# gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkE
# MSIEIKlm5XKKamc0TB8uZG0pFQRCoUZp6hsodUBJZ1F5H6byMEQGCisGAQQBgjcC
# AQwxNjA0oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNy
# b3NvZnQuY29tIDANBgkqhkiG9w0BAQEFAASCAQCnhrbYDCzSQtOiPC4VMPJhk0XO
# wn4PDcMaGBqgXcRGErkcNLrGYJepTUHIJ0zrsgJsrkd7kOZ7P3Vbhd5QXGSoznFu
# OksVZimz5iFbbI+9iCkPnVOpLr5Ieo3aq5EHrcr0FIerjzBp1D6wa9yetXrJ1s2G
# 3u7t7Esro7qSoRuAIxYFjRd6xhnscumWGEkATETdQ94obR/8Jp2ImGPZaX5HI0ns
# aIMKNvCopJLC110IARbgSEriQBJm+sbl3jIJ/Ld2e4UIs54Z9p7I2UhRSg4i/jBV
# nPyKf173Ktm4p88O+pkEv334S+XKkAHCVv4u4lvUgCTmcpj9Lv0+6bGtHcNsoYIX
# rDCCF6gGCisGAQQBgjcDAwExgheYMIIXlAYJKoZIhvcNAQcCoIIXhTCCF4ECAQMx
# DzANBglghkgBZQMEAgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEB
# BgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIOGGyHdpgR0OlXIZSdC6yjY1
# 3zFmPZwq+n9Npyc0S4CsAgZoEseHZMMYEjIwMjUwNTE1MTYxOTUyLjI4WjAEgAIB
# 9KCB2aSB1jCB0zELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEt
# MCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMScw
# JQYDVQQLEx5uU2hpZWxkIFRTUyBFU046NTIxQS0wNUUwLUQ5NDcxJTAjBgNVBAMT
# HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghH7MIIHKDCCBRCgAwIBAgIT
# MwAAAgAL16p/GyoXVgABAAACADANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNDA3MjUxODMxMjFaFw0yNTEwMjIxODMxMjFa
# MIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsT
# Hm5TaGllbGQgVFNTIEVTTjo1MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBAK9V2mnSpD9k5Lp6Exee9/7ReyiTPQ6Ir93HL9upqp1IZr9gzOfYpBE+
# Fp0X6OW4hSB3Oi6qyHqgoE/X0/xpLOVSjvdGUFtmr4fzzB55dJGX1/yOc3VaKFx2
# 3VFJD4mXzV7M1rMJi/VJVqPJs8r/S6fUwLcP6FzmEwMXWEqjgeVM89UNwPLgqTZb
# pkDQyRg2OnEp9DJWLpF5JQKwoaupfimK5eq/1pzql0pJwAaYIErCd96C96J5g4jf
# WFAKWcI5zYfTOpA2p3ks+/P2LQ/9qRqcffy1xC6GsxFBcYcoOCnZqFhjWMHUe/4n
# fNYHjhEevZeXSb+9Uv5h/i8W+i+vdp/LhJgFcOn1bxPnPMI4GGW5WQjTwMpwpw3b
# kS3ZNY7MAqo6jXN1/1iMwOxhrOB1EuGCKwFMfB9gPeLwzYgPAFmu2fx0sEwsiIHl
# W5XV2DNgbcTCqt5J3kaE9uzUO2O5/GU2gI3uwZX47vN7KRj/0FmDWdcGM2FRkcjq
# XQPFpsauVfH+a+B2hvcz3MpDsiaUWcvld0RooIRZrAiVwHDM4ju+h4p8AiIyJpwh
# ShifyGy4x+ie3yV6kT24Ph+q2C2fFwaZlwRR+D02pGVWMQfz/hEGy+SzcNGSDPnr
# n8QpY1eDvpx5DPs4EsfPtOwVWTwSrJaKHm7JoSHATtO+/ZHoXImDAgMBAAGjggFJ
# MIIBRTAdBgNVHQ4EFgQUgCUk2r4JIyqoHucUDl59+X13dzowHwYDVR0jBBgwFoAU
# n6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFt
# cCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcw
# AoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3Nv
# ZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIw
# ADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZI
# hvcNAQELBQADggIBACjwhvZ40bSKkPn7hAoMc1jLEDiNx71u7FfT5hFggjlpU7hg
# iMzYt4m3S2UtG9iAx4NMi67XVbgYtxcVXXrCF7s2MqHyHv2pUwXVeA4Yoy017Qez
# YDp6Oxtdojt7eo8tYT0qrsxi68v9phGQcCLEqEtg/h/txwicTw8oczBaj/qZZbTw
# Agf0DcGe6vhxsmb97/Hrfq0GIPLBdz07lng4N3Uf85NTWsCf3XxQg2JVjXggQi7z
# T0AXHjGFxURSoXElMLO5hXSAw4WacasiCg9lg8BcjSBhHs5/p3eJF0bqXjRMfnkq
# SV8pUQ/tXeOYW+j8ziBewZHD7UbRVtsF4JIy6rU1lpQZL85drjX2Cdwj2VWg8jA2
# ml4Dvh+g4q7CeCBvYpCHfeNfplg3o5I+WmJ/UDekTn6PxzR4NbYpsKRaFIr6gBbu
# oq1mRcOVfsi6/BS3O52zGtpRUosc7ves3Zw7DyJs9HOkrW2MoSkpTN7g0YvVFsnU
# iqpxG7SejJPmLsb86a5LlkCWFn6T77oPsE54qMpFcHNMkVXLHeMTM5550bWQxjEl
# BJfbTFZ3m2EbIcGSMiU7AYC2ZhzO6tkxSv1/feOEpCKsmNtgHLi3tBqqDXwEgiHG
# bc22f8z+JU9vzdKQ259n3wM42ZISPkK6q/fN5kGVsGXa905NTGBJQ04c9g9DMIIH
# 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
# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCA1YwggI+AgEBMIIB
# AaGB2aSB1jCB0zELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEt
# MCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMScw
# JQYDVQQLEx5uU2hpZWxkIFRTUyBFU046NTIxQS0wNUUwLUQ5NDcxJTAjBgNVBAMT
# HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAIyT
# ny2W94r4qS97Ei5VhWy61o5koIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTAwDQYJKoZIhvcNAQELBQACBQDr0GLXMCIYDzIwMjUwNTE1MTI1NTUx
# WhgPMjAyNTA1MTYxMjU1NTFaMHQwOgYKKwYBBAGEWQoEATEsMCowCgIFAOvQYtcC
# AQAwBwIBAAICMWswBwIBAAICEvIwCgIFAOvRtFcCAQAwNgYKKwYBBAGEWQoEAjEo
# MCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG
# 9w0BAQsFAAOCAQEAQ77ymQlS5WTYSI/42woNhO1U287Q03VjI2swhoYE8vP8PbLQ
# ksYzdwLhETzTBzV3BuUhKGr8IhTxWvrcgkxxePb0XlPH98Qrg72DtEJazn+/7vWT
# Lw5B8fcLqUi7q241MCd8HkW0TPEVdho3ps9RUhNKf0Q9e+rc1cIDt1sgG112Sh5v
# 5fbMBDx0TtS+uq0MHp3MoNzuacBToBGTtiqHg58LBFXq+mlQX/+u8nbBdvXW8Sp3
# jqKgVBf7bgyqtTu1CeMqkgs4nhLyfnJSdl4XnFvEBYsmVG7Ib7JXooFbqQzrDzG+
# h1PnK34MJkjRIEIhAU0gESmcXLqUvYF94Hf2KDGCBA0wggQJAgEBMIGTMHwxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACAAvXqn8bKhdWAAEAAAIAMA0G
# CWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJ
# KoZIhvcNAQkEMSIEICPdQfTTxLG5I7RS0ywoHmlzaQXdw+tLve0EQfohTTGaMIH6
# BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQg1Mjt7DWd27qwTQxlAleDXzNoB+Gl
# rkbnSJP/SgJP2ScwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MAITMwAAAgAL16p/GyoXVgABAAACADAiBCAk69hnk2ypzl3qOs+j+isY5phoHVvq
# PjIHX+UbSXzwdTANBgkqhkiG9w0BAQsFAASCAgCItx5OPv5LKkdp3Cl+HVAA8Je8
# tJ0HwizAVnlxlYcDiR3Mt6z2hkmFfyQ2n3DD6CAwYM8wEu9RjOs9GT60Hivj/Fm7
# FaRIw79DDYNwFQSNiaU50y76k5o9CLYwzxj3AmxpYRsBoEQniBtouxAlXMvZbiPt
# wblz8wVHVJKtM3Of5ijOlr4mQqxHI39kiqFFyJLDOZNuZRvELlyXAXGmhHKp0+1I
# yKltv0UtHoIdoMPSk3aa6kVvH41MLuwzbFQNqWJJuinBiEucAsWIJSGpp6DUptOj
# VcammHC83eGf5g8DcEa2hi1y6W2/1pHTsv9YqM2acz7SHI5Eg3nAwKif9O935cUt
# 2uCVOyAAu2ZVFOn8YZPmp84MC1rwYIMJ6dTb1poAAZgdk3HV+j7hBjkS3Rwv7/tA
# JWJ1IeUzf6jqyc3pmyA97rbcvkdWipoiMFgisQtK5/1GDkPM6TFev3pDPpN35vGf
# WQBGSO/tdE25cOFf7ydeL72CfIzi562hxK7i3rDtX9A/d5QZGiTb0EsIvMc1uS21
# uaOgGHjxND8kwWD8g1ARVKzYBCr2yAReCJsFP4e/w+SdmFE4+KCF9M86VybuzVl8
# lrtKC26crdLECw/PuFg6bBbQ6Fjoby/bpHLrHZotbwVEmoMfQtDW8dDi2+YtiKjz
# 7zUkl8gm5lckKyeI/w==
# SIG # End signature block