Classes/install_aszmagent_hci.ps1

[CmdletBinding()]
param (
    [string]$OutFile,
    [string]$AltDownload,
    [string]$Proxy,
    [string]$AltHisEndpoint    
)


$refVersion = [version] '4.5'
$provider = 'Microsoft.HybridCompute'

# Error codes used by azcmagent are in range of [0, 125].
# Installation scripts will use [127, 255]. Check install_linux_azcmagent.sh for the codes used for Linux script.
$global:errorcode="AZCM0150"

#Check if PowerShell is running as administrator
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))
{
    Log-Info -Message "This script must be run as an administrator." -ConsoleOut -Type Error
}

# Ensure TLS 1.2 is accepted. Older PowerShell builds (sometimes) complain about the enum "Tls12" so we use the underlying value
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072
# Ensure TLS 1.3 is accepted, if this .NET supports it (older versions don't)
try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 12288 } catch {}

function Test-AzureStackHCI() {
    [CmdletBinding()]
    param (
    )

    try {
        $product=Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name ProductName | select -ExpandProperty ProductName
    }
    catch {
        Log-Info -Message "Error $_ Unable to determine product SKU from registry" -ConsoleOut -Type Info
        # Will attempt to install anyway
        return $false
    }
    if ($product -eq 'Azure Stack HCI') {
        return $true
    }
    return $false
}

function Test-PowerShellVersion() {
    [CmdletBinding()]
    param (
    )

    Log-Info -Message "PowerShell version: $($PSVersionTable.PSVersion)" -ConsoleOut -Type Info
    return ($PSVersionTable.PSVersion -ge [Version]"3.0")
}

function Test-DotNetFramework() {
    [CmdletBinding()]
    param (
    )

    try {
        $installedVersion = [version] (Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' -Name Version | select -ExpandProperty Version)
    }
    catch {
        Log-Info -Message "Error $_ Unable to determine .NET Framework version" -ConsoleOut -Type Info
        # Will attempt to install anyway
        return $true
    }
    Log-Info -Message ".NET Framework version: $installedVersion" -ConsoleOut -Type Info
    if ($installedVersion -ge $refVersion) {
        return $true
    }
    return $false
}



function Get-MsiLogSummary() {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]$LogPath
    )

    try
    {
        $LogPath = Resolve-Path $LogPath
        Log-Info -Message "Reading Logs from $LogPath" -ConsoleOut -Type Info
        $patterns = @(
            "Installation success or error status",
            "Product: Azure Connected Machine Agent"
        );

        $regex = "(" + ($patterns -join ")|(" ) + ")"

        Write-Verbose "Looking for Patterns: $regex"
        Log-Info -Message "Looking for Patterns: $regex" -ConsoleOut -Type Info

        $inCustomAction = $false
        $logCustomAction = $false
        $caOutput = new-object -TypeName System.Collections.ArrayList
        Get-Content $LogPath | % {
            # log interesting lines
            if ( ($_ -match $regex)) {
                $_ # output to pipeline
            }

            # Wix custom actions start with "Calling custom Action". Gather the log from the CA till we see if it passed
            # At the end, log that output only if it failed with "returned actual error"
            if ($_ -match "Calling custom action") {
                $inCustomAction = $true
                $logCustomAction = $false
            }
            if ($_ -match "MSI \(s\)") {
                $inCustomAction = $false 
            }
            if ($_ -match "returned actual error") {
                $logCustomAction = $true
            }
            if ($inCustomAction) {
                $null = $caOutput.Add($_)
            }
            else
            {
                if($logCustomAction) {
                    $caOutput # output saved lines to pipeline
                }
                $caOutput.Clear()
            }
        }
    } catch {
        # This code is optional so if something goes wrong we'll just swallow the error and have no details
        Log-Info -Message "Error while parsing MSI log: $_" -ConsoleOut -Type Info
    }
}

<# Throw a structured exception#>
function Invoke-Failure
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        $Message,
        [Parameter(Mandatory=$true)]
        $ErrorCode,
        [Parameter(Mandatory=$false)]
        $Details
    )

    $ex = new-object -TypeName System.Exception -ArgumentList @($Message)
    $ex.Data["Details"] = $details
    $ex.Data["ErrorCode"] = $errorcode
    throw $ex
}

function Send-Failure
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Exception] $Error,

        [Parameter(Mandatory = $true)]
        [string] $ErrorCode,

        [Parameter(Mandatory = $false)]
        [string] $AltHisEndpoint

    )

    $hisEndpoint = "https://gbl.his.arc.azure.com"
    if ($env:CLOUD -eq "AzureUSGovernment") {
        $hisEndpoint = "https://gbl.his.arc.azure.us"
    } elseif ($env:CLOUD -eq "AzureChinaCloud") {
        $hisEndpoint = "https://gbl.his.arc.azure.cn"
    } elseif ($env:CLOUD -eq "AzureStackCloud") {
        if ($AltHisEndpoint) {
            $hisEndpoint = $AltHisEndpoint
        }
        else {
            Log-Info -Message "error in Send-Failure due to invalid his endpoint." -ConsoleOut -Type Warning
            return
        }
    }

    $message = "$Error"
    if ($Error.Data["Details"]) {
        $message = $Error.Data["Details"]
    }
    $message = $message.Substring(0, [Math]::Min($message.Length, 600))

    if ( $env:PROVIDER_NAMESPACE ) {
        $provider = $env:PROVIDER_NAMESPACE
    }
    $logBody = @{subscriptionId="$env:SUBSCRIPTION_ID";resourceGroup="$env:RESOURCE_GROUP";tenantId="$env:TENANT_ID";location="$env:LOCATION";correlationId="$env:CORRELATION_ID";authType="$env:AUTH_TYPE";operation="onboarding";namespace="$provider";osType="windows";messageType="$ErrorCode";message="$message";}
    
    Invoke-WebRequest -UseBasicParsing -Uri "$hisEndpoint/log" -Method "PUT" -Body ($logBody | ConvertTo-Json) -ErrorAction SilentlyContinue | out-null
}

# Based on the MSI error code, we may have some hint to provide as to the issue
# See https://learn.microsoft.com/en-us/windows/win32/msi/error-codes
function Get-MsiErrorDetails() {
    [CmdletBinding()]
    param(
        $exitCode
    )

    $message = (net helpmsg $exitCode) -join ""
    $hint = ""
    $errorCode = "AZCM0149" # exitCode is the return value from msiexec. errorCode is the error code of the script
    switch($exitCode) {
        1633 {
            # ERROR_INSTALL_PLATFORM_UNSUPPORTED
            $hint = "Unsupported: Azure Connected Machine Agent is only compatible with X64 operating systems"
            $errorCode = "AZCM0153"
        }
    }
    return [PSCustomObject]@{
        Message = $message
        Hint = $hint
        ErrorCode = $errorCode
    }
}

function Check-Physical-Memory() {
    [CmdletBinding()]
    param (
    )

    $memory = systeminfo | Select-String '^Total Physical Memory'
    Log-Info -Message "$memory" -ConsoleOut -Type Info
}

try {
    Log-Info -Message "Installing Azure Connected Machine Agent" -ConsoleOut -Type Info
    Check-Physical-Memory
    
    #TODO: need to uncomment this after local testing
    # $hci = Test-AzureStackHCI
    # if (-not $hci)
    # {
    # Invoke-Failure -Message "This server is NOT running Azure Stack HCI , this module is only meant for Azure Stack HCI servers" -ErrorCode "AZCM0152"
    # }

    $validPowerShell = Test-PowerShellVersion
    if (-Not $validPowerShell) 
    {
        Invoke-Failure -Message "Azure Connected Machine Agent requires PowerShell version 4.0 or later" -ErrorCode "AZCM0154"
    }

    $validFramework = Test-DotNetFramework
    if (-Not $validFramework) 
    {
        Invoke-Failure -Message "Azure Connected Machine Agent requires .NET Framework version $refVersion or later" -ErrorCode "AZCM0151"
    }
    
    # Download the package
    $msiFile = Join-Path $env:Temp "AzureConnectedMachineAgent.msi"
        
    try {
        if ($AltDownload) {
            $downloadUri = $AltDownload
        }
        else {
            $downloadUri = "https://aka.ms/AzureConnectedMachineAgent" 
        }

        if (([Uri]$downloadUri).Scheme -in @("https","http"))
        {
            Log-Info -Message "Downloading agent package from $downloadUri to $msiFile" -ConsoleOut -Type Info
            # It's a web site, download it
            if ($Proxy) {
                Invoke-WebRequest -UseBasicParsing -Proxy $Proxy -Uri $downloadUri -OutFile $msiFile
            } else {
                Invoke-WebRequest -UseBasicParsing -Uri $downloadUri -OutFile $msiFile
            }
        }
        else {
            # This could be a UNC path or a local file, just try and copy it
            Log-info -Message "Copying agent package from $downloadUri to $msiFile" -ConsoleOut -Type Info
            Copy-Item $downloadUri $msiFile
        }
    } 
    catch {
        Invoke-Failure -ErrorCode "AZCM0148" -Message "Download failed: $_"
    }
        
    # Install the package
    $logFile = Join-Path $env:Temp "installationlog.txt"
    Log-Info -Message "Installing agent package" -ConsoleOut -Type Info
    $exitCode = (Start-Process -FilePath msiexec.exe -ArgumentList @("/i", $msiFile , "/l*v", $logFile, "/qn") -Wait -Passthru).ExitCode
    if ($exitCode -ne 0) {
        $details = (Get-MsiErrorDetails $exitCode)
        $logInfo = ((Get-MsiLogSummary $logFile) -join "`n")
        Invoke-Failure -Message "Installation failed: [$exitCode]: $($details.Message) $($details.Hint)`: See $logFile for additional details." -ErrorCode $details.ErrorCode -Details $logInfo
    }

    # Check if we need to set proxy environment variable
    if ($Proxy) {
        Log-Info -Message "Setting proxy configuration: $Proxy" -ConsoleOut -Type Info 
        & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent" config set proxy.url ${Proxy}
    }
    
} catch {
    $code = $_.Exception.Data.ErrorCode
    $details = $_.Exception.Data.Details
    if(!$code) { $code = "AZCM0150" } # default if we do not have some more specific error
    if ($OutFile) {
        [ordered]@{
            status  = "failed"
            error = [ordered]@{
                message = $_.Exception.Message
                code = $code
                details = $details
            }
        } | ConvertTo-Json | Out-File $OutFile
    }
    Log-Info -Message "Installation failed: $_" -ConsoleOut -Type Error

    if ($details) {
        Write-Output "Details: $details"
    }
    Send-Failure $_.Exception $code $AltHisEndpoint
    return 1
}

# Installation was successful if we got this far
if ($OutFile) {
    [ordered]@{
        status  = "success"
        message = "Installation of azcmagent completed successfully"
    } | ConvertTo-Json | Out-File $OutFile
}

Log-Info -Message "Installation of azcmagent completed successfully" -ConsoleOut
return 0

# SIG # Begin signature block
# MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCoJ9L+ZnR+2L/T
# CrflgU91bqZwiu3kozF2rz86W63KuaCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIE3ed5f+BS1NZI42v9iFfOHe
# yRbmcsmJ/IhzQw3PjsKoMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAavTYxD3u7VYo5QfFGV3yrsWykHDaeC4+oOdKeKc1mXkk9qJFI24U73rM
# WOeItNtN781q6d0gAHZwlqbFGZiIbhc9qZMVDgsa+6gDvAQ/v0V31r03q0nFyB1S
# c3Md8NVdyYnFrUHodiSFBrTmg09xVhen2/k3tkLX0O3um5Mq883U2JIOTa5669zH
# z5StvdqoP9vbCFeCHXrMWxM/XBcgQCvI75yFTWSRCkDO087glaJLL4XDM5vCzx9a
# fUR+lLYcst9WbXMHgqUOJBBzkrhHGPV2/fstxh6zdSE+NKjtnJxE0GjoVeo82udW
# hjpHT8RF+Re/RMDqo5hrIghgxNArkaGCFykwghclBgorBgEEAYI3AwMBMYIXFTCC
# FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCDrv/RQpBN4cieXFIP7N3VQQxYYDYV6k+lM1SrF1Gc2EgIGZUKtHaLT
# GBMyMDIzMTEwODEyNTQzNy4wOTRaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjJBRDQtNEI5Mi1GQTAxMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAHenkielp8oRD0AAQAAAd4wDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMx
# MDEyMTkwNzEyWhcNMjUwMTEwMTkwNzEyWjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoyQUQ0LTRC
# OTItRkEwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALSB9ByF9UIDhA6xFrOniw/x
# sDl8sSi9rOCOXSSO4VMQjnNGAo5VHx0iijMEMH9LY2SUIBkVQS0Ml6kR+TagkUPb
# aEpwjhQ1mprhRgJT/jlSnic42VDAo0en4JI6xnXoAoWoKySY8/ROIKdpphgI7OJb
# 4XHk1P3sX2pNZ32LDY1ktchK1/hWyPlblaXAHRu0E3ynvwrS8/bcorANO6Djuysy
# S9zUmr+w3H3AEvSgs2ReuLj2pkBcfW1UPCFudLd7IPZ2RC4odQcEPnY12jypYPnS
# 6yZAs0pLpq0KRFUyB1x6x6OU73sudiHON16mE0l6LLT9OmGo0S94Bxg3N/3aE6fU
# bnVoemVc7FkFLum8KkZcbQ7cOHSAWGJxdCvo5OtUtRdSqf85FklCXIIkg4sm7nM9
# TktUVfO0kp6kx7mysgD0Qrxx6/5oaqnwOTWLNzK+BCi1G7nUD1pteuXvQp8fE1Kp
# TjnG/1OJeehwKNNPjGt98V0BmogZTe3SxBkOeOQyLA++5Hyg/L68pe+DrZoZPXJa
# GU/iBiFmL+ul/Oi3d83zLAHlHQmH/VGNBfRwP+ixvqhyk/EebwuXVJY+rTyfbRfu
# h9n0AaMhhNxxg6tGKyZS4EAEiDxrF9mAZEy8e8rf6dlKIX5d3aQLo9fDda1ZTOw+
# XAcAvj2/N3DLVGZlHnHlAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUazAmbxseaapg
# dxzK8Os+naPQEsgwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAOKUwHsXDacGOvUI
# gs5HDgPs0LZ1qyHS6C6wfKlLaD36tZfbWt1x+GMiazSuy+GsxiVHzkhMW+FqK8gr
# uLQWN/sOCX+fGUgT9LT21cRIpcZj4/ZFIvwtkBcsCz1XEUsXYOSJUPitY7E8bbld
# mmhYZ29p+XQpIcsG/q+YjkqBW9mw0ru1MfxMTQs9MTDiD28gAVGrPA3NykiSChvd
# qS7VX+/LcEz9Ubzto/w28WA8HOCHqBTbDRHmiP7MIj+SQmI9VIayYsIGRjvelmNa
# 0OvbU9CJSz/NfMEgf2NHMZUYW8KqWEjIjPfHIKxWlNMYhuWfWRSHZCKyIANA0aJL
# 4soHQtzzZ2MnNfjYY851wHYjGgwUj/hlLRgQO5S30Zx78GqBKfylp25aOWJ/qPhC
# +DXM2gXajIXbl+jpGcVANwtFFujCJRdZbeH1R+Q41FjgBg4m3OTFDGot5DSuVkQg
# jku7pOVPtldE46QlDg/2WhPpTQxXH64sP1GfkAwUtt6rrZM/PCwRG6girYmnTRLL
# sicBhoYLh+EEFjVviXAGTk6pnu8jx/4WPWu0jsz7yFzg82/FMqCk9wK3LvyLAyDH
# N+FxbHAxtgwad7oLQPM0WGERdB1umPCIiYsSf/j79EqHdoNwQYROVm+ZX10RX3n6
# bRmAnskeNhi0wnVaeVogLMdGD+nqMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# tB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoy
# QUQ0LTRCOTItRkEwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAaKBSisy4y86pl8Xy22CJZExE2vOggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOj1vQgwIhgPMjAyMzExMDgxNTUzNDRaGA8yMDIzMTEwOTE1NTM0NFowdDA6Bgor
# BgEEAYRZCgQBMSwwKjAKAgUA6PW9CAIBADAHAgEAAgIJ9TAHAgEAAgISiDAKAgUA
# 6PcOiAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBABrP0Q9xtBbvLiquNkqO
# tAAMwHx5cXcbzHlZFIA9TZiL36J9tXWkcp88EbvWWsujH1pc6ugCNOEAiClH6lNI
# M5ZDLpBX+BGQ3GTgQvGNxo3JIlsvfu9d/iou9ggK+J8TdFstIeFnR8Sj7b1EcjNk
# GvwoG/b5bOBKcqHbq1udw/TfMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTACEzMAAAHenkielp8oRD0AAQAAAd4wDQYJYIZIAWUDBAIB
# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx
# IgQgxkO0HfSZpKspmndAbmay12ImAZol7AOnTHnxwHCLzk4wgfoGCyqGSIb3DQEJ
# EAIvMYHqMIHnMIHkMIG9BCCOPiOfDcFeEBBJAn/mC3MgrT5w/U2z81LYD44Hc34d
# ezCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB3p5I
# npafKEQ9AAEAAAHeMCIEIPuAv+VN+Jk3HFrEtWWxgy94QHpydsjz/jVxKuFd8sAw
# MA0GCSqGSIb3DQEBCwUABIICACUHatGZIErYysH1qK5dvsTr2kGUjaiAEqJsgN5U
# KNj4avgpl514Smu7ISZW254yvokj8Nzssy6v2cy19MnnZK0+Ao4VF5Ln0XTnFsui
# WSfVjmUH+so1YxR0kCqHIvej/6FhENlYbM4Uh66jeQe/OE7alDj/R6u4qBXDEbmi
# Nc6XURhkCWGvQLhErAYw4L21WHjBSjS5v/gSL0z32qgTFHHIfNS4tKe0f9sgMGpW
# KwqJddkDcc38G1OxaJVSWBF3tjDF0N6kCVqSYaTIfb6zHkkbd5mUTzAZBTHt55VK
# EdUPkhwwFqCc9VMX13nouy9YtCBQ/4HzrVO6aX44xMMcVJByyLDU4nibRQ1vYUM3
# Sqmh82VQvmdnE+mKDx5S4kr8Ry+23RExPxIX9B+j//gaioBHzQ7M5bkcADxCybG3
# QwlG12fo9k6Zt/x63yfaijikmi4VXhC3RZvvVYtYaddV1U6UhVnITs/4E3S/8YTM
# iZeB5Lu8Mq2PLiiBS/EcGNntYc4QOkrON1fcBafmImBMkk4q/pogUZ4O/lvXKu7E
# 823Yk01597G+ZIW/DMMo2jxeYmDdw/CzvwmMtDJFQc5iOgf05bzyanrXmxhsBDuM
# B4Gr/zWDMsPqPpRKRJ6gQaqUJiNDryvNpvOvLVfwSvM1Igx7d65pUB0vOVLw2Rhz
# hVZD
# SIG # End signature block