Utils.psm1

function Invoke-CommandLine {
    <#
    .SYNOPSIS
        Execute a command, and check its exit code.
 
    .PARAMETER Command
        Command to execute. It can be a binary file path, or a binary file name found in PATH.
 
    .PARAMETER Arguments
        Arguments to pass to the command.
 
    #>

    Param(
        [Parameter(Mandatory=$true)]
        [String]$Command,

        [String]$Arguments
    )
    & $Command $Arguments.Split(" ")
    if($LASTEXITCODE) {
        Throw "$Command $Arguments returned a non zero exit code ${LASTEXITCODE}."
    }
}

function Add-ToPathEnvVar {
    <#
    .SYNOPSIS
        Append paths to the PATH environment variable.
 
    .PARAMETER Path
        List of paths to be appended to the PATH environment variable.
 
    .PARAMETER Target
        The PATH environment variable target. Valid values are: Machine, User.
 
    #>

    Param(
        [Parameter(Mandatory=$true)]
        [String[]]$Path,

        [Parameter(Mandatory=$false)]
        [ValidateSet([System.EnvironmentVariableTarget]::User, [System.EnvironmentVariableTarget]::Machine)]
        [System.EnvironmentVariableTarget]$Target=[System.EnvironmentVariableTarget]::User
    )
    $pathEnvVar = [Environment]::GetEnvironmentVariable("PATH", $Target).Split(';')
    $currentSessionPath = $env:PATH.Split(';')
    foreach($p in $Path) {
        if($p -notin $pathEnvVar) {
            $pathEnvVar += $p
        }
        if($p -notin $currentSessionPath) {
            $currentSessionPath += $p
        }
    }
    $env:PATH = $currentSessionPath -join ';'
    $newPathEnvVar = $pathEnvVar -join ';'
    [Environment]::SetEnvironmentVariable("PATH", $newPathEnvVar, $Target)
}

function Start-FileDownload {
    <#
    .SYNOPSIS
        Download a file from the given URL.
 
    .PARAMETER URL
        The file download URL.
 
    .PARAMETER Destination
        The local path where the file will be downloaded.
 
    .PARAMETER RetryCount
        The number of retries for the download operation.
 
    #>

    Param(
        [Parameter(Mandatory=$true)]
        [String]$URL,

        [Parameter(Mandatory=$true)]
        [String]$Destination,

        [Int]$RetryCount=10
    )
    Start-ExecuteWithRetry `
        -ScriptBlock { Invoke-CommandLine -Command "curl.exe" -Arguments "-L -s -o $Destination $URL" } `
        -MaxRetryCount $RetryCount `
        -RetryMessage "Failed to download $URL. Retrying"
}

function Invoke-Kubectl {
    <#
    .SYNOPSIS
        Execute a kubectl command.
 
    .PARAMETER Arguments
        Arguments to pass to the command.
 
    #>

    Param(
        [Parameter(Mandatory=$true)]
        [String]$Arguments
    )

    Invoke-CommandLine -Command "kubectl.exe" -Arguments $Arguments
}

function Install-KubernetesManifests {
    <#
    .SYNOPSIS
        Install the Kubernetes manifests given as parameter. The manifests are written into a temporary file, which is given to 'kubectl apply -f <file_path>'.
 
    .PARAMETER Manifests
        The Kubernetes manifests.
 
    #>

    Param(
        [Parameter(Mandatory=$true)]
        [String[]]$Manifests
    )
    $tempFile = New-TemporaryFile
    try {
        foreach($manifest in $Manifests) {
            Set-Content -Path $tempFile -Value $manifest
            Invoke-Kubectl "apply -f ${tempFile}"
        }
    } finally {
        Remove-Item $tempFile
    }
}

function Uninstall-KubernetesManifests {
    <#
    .SYNOPSIS
        Uninstall the Kubernetes manifests given as parameter. The manifests are written into a temporary file, which is given to 'kubectl delete -f <file_path>'.
 
    .PARAMETER Manifests
        The Kubernetes manifests.
 
    #>

    Param(
        [Parameter(Mandatory=$true)]
        [String[]]$Manifests
    )
    $tempFile = New-TemporaryFile
    try {
        foreach($manifest in $Manifests) {
            Set-Content -Path $tempFile -Value $manifest
            Invoke-Kubectl "delete --ignore-not-found=true -f ${tempFile}"
        }
    } finally {
        Remove-Item $tempFile
    }
}

function Start-ExecuteWithRetry {
    <#
    .SYNOPSIS
        Execute the given PowerShell script block until it succeeds, or until the maximum retries count is reached.
 
    .PARAMETER ScriptBlock
        The script block to run.
 
    .PARAMETER MaxRetryCount
        The number of retries before we throw an exception.
 
    .PARAMETER RetryInterval
        Number of seconds to sleep between retries.
 
    .PARAMETER RetryMessage
        Warning message logged on every failed retry.
 
    .PARAMETER ArgumentList
        Arguments to pass to your wrapped commandlet/command.
 
    #>

    Param(
        [Parameter(Mandatory=$true)]
        [ScriptBlock]$ScriptBlock,

        [Int]$MaxRetryCount=10,

        [Int]$RetryInterval=3,

        [String]$RetryMessage,

        [Array]$ArgumentList=@()
    )
    $currentErrorActionPreference = $ErrorActionPreference
    $ErrorActionPreference = "Continue"

    $retryCount = 0
    while ($true) {
        try {
            $res = Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList
            $ErrorActionPreference = $currentErrorActionPreference
            return $res
        } catch [System.Exception] {
            $retryCount++
            if ($retryCount -gt $MaxRetryCount) {
                $ErrorActionPreference = $currentErrorActionPreference
                Throw $_
            } else {
                $prefixMsg = "Retry(${retryCount}/${MaxRetryCount})"
                if($RetryMessage) {
                    Write-Host "${prefixMsg} - $RetryMessage"
                } elseif($_) {
                    Write-Host "${prefixMsg} - $($_.ToString())"
                }
                Start-Sleep $RetryInterval
            }
        }
    }
}

function Get-UniqueId {
    <#
    .SYNOPSIS
        Get an unique id as string. This is based on the value returned from the cmdlet 'New-Guid'.
 
    .PARAMETER Length
        The length of the id string. Minimum allowed is 4, and the maximum is 32. Defaults to 8.
 
    #>

    Param(
        [ValidateRange(4, 32)]
        [Int]$Length=8
    )
    return (New-Guid).Guid.Replace("-", "").Substring(0, $Length)
}

function Get-InputParameter {
    <#
    .SYNOPSIS
        Get an input parameter by interactively asking the user with a prompt.
 
    .PARAMETER Prompt
        The prompt text.
 
    .PARAMETER Description
        The input parameter description.
 
    .PARAMETER Default
        The input parameter default value. Only used when 'Optional' flag is used, and the input parameter is empty.
 
    .PARAMETER Type
        The input parameter will be casted to this type.
 
    .PARAMETER Optional
        If this is enabled, the function allows empty input parameters given by the user.
 
    .PARAMETER AsSecureString
        Flag used if the input parameter is a password. The returned value will
        be a secure string.
 
    .PARAMETER ValidationScriptBlock
        The script block used to validate the input given. It should raise an
        exception (with a meaningful message) if something is wrong with the input
        parameter (which referred as $_ in the script block).
 
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [String]$Prompt,

        [String]$Description,

        $Default,

        [ValidateScript({
            if(!($_ -as [type])) {
                Throw "Type '$_' is invalid."
            }
            $true
        })]
        [String]$Type="String",

        [Switch]$Optional,

        [Switch]$AsSecureString,

        [ScriptBlock]$ValidationScriptBlock={$true}
    )
    if($Description) {
        Write-Host -ForegroundColor Green $Description
    }
    if($AsSecureString) {
        $Type = "SecureString"
    }
    do {
        $valid = $true
        try {
            $param = Read-Host -Prompt $Prompt -AsSecureString:$AsSecureString
            if(!$param) {
                if(!$Optional) {
                    Throw "This input parameter is mandatory."
                }
                $param = $Default
            }
            if($AsSecureString -and ![System.Net.NetworkCredential]::new('', $param).Password) {
                Throw "The secret input cannot be empty."
            }
            if($AsSecureString) {
                $paramAgain = Read-Host -Prompt "${Prompt} (again)" -AsSecureString:$AsSecureString
                if([System.Net.NetworkCredential]::new('', $param).Password -ne [System.Net.NetworkCredential]::new('', $paramAgain).Password) {
                    Throw "The repeated secret input parameter does not match."
                }
            }
            $paramType = $Type -as [Type]
            if($paramType.BaseType -eq [Array]) {
                $param = $param.Split(',').Trim()
            }
            $param = [System.Management.Automation.LanguagePrimitives]::ConvertTo($param, $paramType)
            $param | Where-Object $ValidationScriptBlock | Out-Null
        } catch [System.Exception] {
            Write-Warning $_.ToString()
            $valid = $false
        }
    } while(!$valid)
    return $param
}

# SIG # Begin signature block
# MIInvQYJKoZIhvcNAQcCoIInrjCCJ6oCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCqT1gdsE4Yrkq6
# xnvoCy6NcVE63EApk/R1oO9sH4eHPqCCDYUwggYDMIID66ADAgECAhMzAAACU+OD
# 3pbexW7MAAAAAAJTMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzAwWhcNMjIwOTAxMTgzMzAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDLhxHwq3OhH+4J+SX4qS/VQG8HybccH7tnG+BUqrXubfGuDFYPZ29uCuHfQlO1
# lygLgMpJ4Geh6/6poQ5VkDKfVssn6aA1PCzIh8iOPMQ9Mju3sLF9Sn+Pzuaie4BN
# rp0MuZLDEXgVYx2WNjmzqcxC7dY9SC3znOh5qUy2vnmWygC7b9kj0d3JrGtjc5q5
# 0WfV3WLXAQHkeRROsJFBZfXFGoSvRljFFUAjU/zdhP92P+1JiRRRikVy/sqIhMDY
# +7tVdzlE2fwnKOv9LShgKeyEevgMl0B1Fq7E2YeBZKF6KlhmYi9CE1350cnTUoU4
# YpQSnZo0YAnaenREDLfFGKTdAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUlZpLWIccXoxessA/DRbe26glhEMw
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2NzU5ODAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AKVY+yKcJVVxf9W2vNkL5ufjOpqcvVOOOdVyjy1dmsO4O8khWhqrecdVZp09adOZ
# 8kcMtQ0U+oKx484Jg11cc4Ck0FyOBnp+YIFbOxYCqzaqMcaRAgy48n1tbz/EFYiF
# zJmMiGnlgWFCStONPvQOBD2y/Ej3qBRnGy9EZS1EDlRN/8l5Rs3HX2lZhd9WuukR
# bUk83U99TPJyo12cU0Mb3n1HJv/JZpwSyqb3O0o4HExVJSkwN1m42fSVIVtXVVSa
# YZiVpv32GoD/dyAS/gyplfR6FI3RnCOomzlycSqoz0zBCPFiCMhVhQ6qn+J0GhgR
# BJvGKizw+5lTfnBFoqKZJDROz+uGDl9tw6JvnVqAZKGrWv/CsYaegaPePFrAVSxA
# yUwOFTkAqtNC8uAee+rv2V5xLw8FfpKJ5yKiMKnCKrIaFQDr5AZ7f2ejGGDf+8Tz
# OiK1AgBvOW3iTEEa/at8Z4+s1CmnEAkAi0cLjB72CJedU1LAswdOCWM2MDIZVo9j
# 0T74OkJLTjPd3WNEyw0rBXTyhlbYQsYt7ElT2l2TTlF5EmpVixGtj4ChNjWoKr9y
# TAqtadd2Ym5FNB792GzwNwa631BPCgBJmcRpFKXt0VEQq7UXVNYBiBRd+x4yvjqq
# 5aF7XC5nXCgjbCk7IXwmOphNuNDNiRq83Ejjnc7mxrJGMIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGY4wghmKAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAJT44Pelt7FbswAAAAA
# AlMwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIDbn
# WHxCe4pUqg9hKC6+aU3tat+FCKE863SDn4+acIayMEQGCisGAQQBgjcCAQwxNjA0
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQu
# Y29tIDANBgkqhkiG9w0BAQEFAASCAQBG6bgNRn9qgi8JjuLVwn6tlykuoMJVLX0S
# TqDG0T60iC5WtoQYHFH/HxMnfe1uOStPoxGr1TM5ANNiX25o/LBF8F2B63+5Et/N
# Y4qJYPBnMM63aVUAvuglP7dZAvOiMEb1WJfb1PMTSGh2l7GFNatiNWr1wn7uH24l
# JQQEfYregpNueNik+FKKpgNNr5K+zMCPuM6qtLOgzauIgSztOK1IGoRouJ8ESsmd
# m74XjYaFUhJEITl3t6fsDP7R65E0BZlABHUHi5DKucnq/iojs7p7e+hi97JJIgom
# wdysykdCMX1tCApkRGeIpKPoWvvbq0qnIcT6vVUKBadmDiYsYVlfoYIXFjCCFxIG
# CisGAQQBgjcDAwExghcCMIIW/gYJKoZIhvcNAQcCoIIW7zCCFusCAQMxDzANBglg
# hkgBZQMEAgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEE
# AYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIHrZU3dYT7dmSO6CmqGcIY2YDFmFyc3n
# +L8nW/WKKYkHAgZics7I8YkYEzIwMjIwNTEzMTU0NTEzLjU2NlowBIACAfSggdik
# gdUwgdIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNV
# BAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UE
# CxMdVGhhbGVzIFRTUyBFU046RDA4Mi00QkZELUVFQkExJTAjBgNVBAMTHE1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghFlMIIHFDCCBPygAwIBAgITMwAAAY/z
# UajrWnLdzAABAAABjzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt
# cCBQQ0EgMjAxMDAeFw0yMTEwMjgxOTI3NDZaFw0yMzAxMjYxOTI3NDZaMIHSMQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNy
# b3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxl
# cyBUU1MgRVNOOkQwODItNEJGRC1FRUJBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
# mVc+/rXPFx6Fk4+CpLrubDrLTa3QuAHRVXuy+zsxXwkogkT0a+XWuBabwHyqj8RR
# iZQQvdvbOq5NRExOeHiaCtkUsQ02ESAe9Cz+loBNtsfCq846u3otWHCJlqkvDrSr
# 7mMBqwcRY7cfhAGfLvlpMSojoAnk7Rej+jcJnYxIeN34F3h9JwANY360oGYCIS7p
# LOosWV+bxug9uiTZYE/XclyYNF6XdzZ/zD/4U5pxT4MZQmzBGvDs+8cDdA/stZfj
# /ry+i0XUYNFPhuqc+UKkwm/XNHB+CDsGQl+ZS0GcbUUun4VPThHJm6mRAwL5y8zp
# tWEIocbTeRSTmZnUa2iYH2EOBV7eCjx0Sdb6kLc1xdFRckDeQGR4J1yFyybuZsUP
# 8x0dOsEEoLQuOhuKlDLQEg7D6ZxmZJnS8B03ewk/SpVLqsb66U2qyF4BwDt1uZkj
# EZ7finIoUgSz4B7fWLYIeO2OCYxIE0XvwsVop9PvTXTZtGPzzmHU753GarKyuM6o
# a/qaTzYvrAfUb7KYhvVQKxGUPkL9+eKiM7G0qenJCFrXzZPwRWoccAR33PhNEuuz
# zKZFJ4DeaTCLg/8uK0Q4QjFRef5n4H+2KQIEibZ7zIeBX3jgsrICbzzSm0QX3SRV
# mZH//Aqp8YxkwcoI1WCBizv84z9eqwRBdQ4HYcNbQMMCAwEAAaOCATYwggEyMB0G
# A1UdDgQWBBTzBuZ0a65JzuKhzoWb25f7NyNxvDAfBgNVHSMEGDAWgBSfpxVdAF5i
# XYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
# JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRw
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRp
# bWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4ICAQDNf9Oo9zyhC5n1jC8i
# U7NJY39FizjhxZwJbJY/Ytwn63plMlTSaBperan566fuRojGJSv3EwZs+RruOU2T
# /ZRDx4VHesLHtclE8GmMM1qTMaZPL8I2FrRmf5Oop4GqcxNdNECBClVZmn0KzFdP
# MqRa5/0R6CmgqJh0muvImikgHubvohsavPEyyHQa94HD4/LNKd/YIaCKKPz9SA5f
# Aa4phQ4Evz2auY9SUluId5MK9H5cjWVwBxCvYAD+1CW9z7GshJlNjqBvWtKO6J0A
# emfg6z28g7qc7G/tCtrlH4/y27y+stuwWXNvwdsSd1lvB4M63AuMl9Yp6au/XFkn
# GzJPF6n/uWR6JhQvzh40ILgeThLmYhf8z+aDb4r2OBLG1P2B6aCTW2YQkt7TpUnz
# I0cKGr213CbKtGk/OOIHSsDOxasmeGJ+FiUJCiV15wh3aZT/VT/PkL9E4hDBAwGt
# 49G88gSCO0x9jfdDZWdWGbELXlSmA3EP4eTYq7RrolY04G8fGtF0pzuZu43A29za
# I9lIr5ulKRz8EoQHU6cu0PxUw0B9H8cAkvQxaMumRZ/4fCbqNb4TcPkPcWOI24QY
# lvpbtT9p31flYElmc5wjGplAky/nkJcT0HZENXenxWtPvt4gcoqppeJPA3S/1D57
# KL3667epIr0yV290E2otZbAW8DCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkA
# AAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVow
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX
# 9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1q
# UoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8d
# q6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byN
# pOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2k
# rnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4d
# Pf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgS
# Uei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8
# QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6Cm
# gyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzF
# ER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQID
# AQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQU
# KqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1
# GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0
# bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMA
# QTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbL
# j+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1p
# Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz
# LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwU
# tj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN
# 3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU
# 5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5
# KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGy
# qVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB6
# 2FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltE
# AY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFp
# AUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcd
# FYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRb
# atGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQd
# VTNYs6FwZvKhggLUMIICPQIBATCCAQChgdikgdUwgdIxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5k
# IE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046RDA4
# Mi00QkZELUVFQkExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZp
# Y2WiIwoBATAHBgUrDgMCGgMVAD5NL4IEdudIBwdGoCaV0WBbQZpqoIGDMIGApH4w
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDm
# KIGgMCIYDzIwMjIwNTEzMTUwNTA0WhgPMjAyMjA1MTQxNTA1MDRaMHQwOgYKKwYB
# BAGEWQoEATEsMCowCgIFAOYogaACAQAwBwIBAAICDMswBwIBAAICEUwwCgIFAOYp
# 0yACAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAweh
# IKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQCShfrnIf+qWCd7vkkguWnn
# 13c+rGwTlz0k/I3dGHpsw44vSvGcquyYoirqNsxFaY7Zi12Hif4clYp3ZLmpNYPt
# 0iR3gx3rtYCJxgE+pk0dc0yRTsPyuO6NdEJ7l9T7+P8iltuFxmbvrPi0l1XOUbIk
# DSl1iVeAhoEDVlkttKUNCTGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFBDQSAyMDEwAhMzAAABj/NRqOtact3MAAEAAAGPMA0GCWCGSAFlAwQCAQUA
# oIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIE
# ILq5cbQN3NwEXzHIYNsgLMmH4A9+Djp6FgKwZcWLswL7MIH6BgsqhkiG9w0BCRAC
# LzGB6jCB5zCB5DCBvQQgl3IFT+LGxguVjiKm22ItmO6dFDWW8nShu6O6g8yFxx8w
# gZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw
# JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAY/zUajr
# WnLdzAABAAABjzAiBCBDmfdzhSzUjwR7MGfEyS41rhVz+KnkPZ/sJsI1GTXdyjAN
# BgkqhkiG9w0BAQsFAASCAgBwcyTbb7aEe8rRbez/SvIwNJAGjf92yYzOM+muyfb3
# UDdapdk74njyYnE3V5WQsHHCTbTWk85kU9EpB/WaTuxC1XfsYRxXGTaNR1SvS8+w
# CoYktXr2tGaH+Ybj3BvGAVDuiZ2JuP3gmLEYpL9aY2N8X6S0/xsv5VLqEgST7UiJ
# sx37jwJu9lTu0YU5MnaXnu82kMZoFZnMxgvctiZ0FPJGz43hfKdb2e7M8CF8zsa/
# CaySInQXaNb8RYUCSeW2jZ4U/Ljrl5yQgNiXLEq9HJmv2crMlE7vEJvoaun0gr/j
# taK454lZOnSD74qEUMPuikdM9UO/c5wqxPq3goHYwjJQYHKidWR3IVIhhwYz3vwg
# NSV/nlbSFw0aSWmUtvLqnTm/olDMJbyzBSFNsdMFvsfAYdVgSv0x/YaCbDth10AG
# iXs2M6MfrH0yInNKEqaUk3Jdo9HP6lGh2gSbFE+cVDgIaMzf2qua1lOewYIzRFt1
# xOArcoWBd78+On5k3lRJEgW1fG9LuOq02cxf+8EMxKqROIyYTaa9u3I2cjlw9Ygy
# nQSc2xk+jWtzgcBUUPERhyokKjno7Eug79ykq+eknlHjlPI0TCFDAL8Z5snKVb9T
# wWOUM9zU3UKiOSj7pe0f7M0T7UNSQEN0Ee1z1nfT8pDVvjYDos+RZ/JJ591Sj8G7
# Bg==
# SIG # End signature block