AzStackHciValidatedRecipe/AzStackHci.ValidatedRecipe.Helpers.psm1

Import-LocalizedData -BindingVariable lswTxt -FileName AzStackHci.ValidatedRecipe.Strings.psd1

function TestResult {
    <#
    .SYNOPSIS
        Build up a params data structure to pass the
        New-AzStackHciResultObject function.
    .DESCRIPTION
        The New-AzStackHciResultObject function get information about the
        result of a test into the correct result files (log, json, etc.)
    #>

    Param([Parameter(Mandatory=$true,Position=0)] [array]$responses,
          [Parameter(Mandatory=$true,Position=1)] [string]$Name,
          [Parameter(Mandatory=$true,Position=2)] [string]$Title,
          [Parameter(Mandatory=$true,Position=3)] [string]$DisplayName,
          [Parameter(Mandatory=$true,Position=4)] [string]$Severity,
          [Parameter(Mandatory=$true,Position=5)] [string]$Description,
          [Parameter(Mandatory=$false,Position=6)] [string]$Remediation = 'https://learn.microsoft.com/en-us/azure/azure-local/update/update-troubleshooting-23h2',
          [Parameter(Mandatory=$false,Position=7)] [string]$TargetResourceType = 'ValidatedRecipe',
          [Parameter(Mandatory=$false,Position=8)] [string]$Resource = 'Validated Assembly Recipe')
    $instanceResults = @()
    foreach ($response in $responses) {
        $detailString = $($response.details) -join '; '
        foreach ($msg in $($response.logLines)) {
            $msgArray = $msg.split('|')
            Log-Info $msgArray[0] -Type $msgArray[1]
        }
        try {
            $Status = 'SUCCESS'
            if ($($response.rc)) {
                $Status = 'FAILURE'
            }
            $params = @{
                Name               = $Name
                Title              = $Title
                DisplayName        = $DisplayName
                Severity           = $Severity
                Description        = $Description
                Tags               = @{}
                Remediation        = $Remediation
                TargetResourceID   = $($response.computername)
                TargetResourceName = $($response.computername)
                TargetResourceType = $TargetResourceType
                Timestamp          = [datetime]::UtcNow
                Status             = $status
                HealthCheckSource  = $ENV:EnvChkrId
                AdditionalData     = @{Source    = $($response.computername)
                                       Resource  = $Resource
                                       Detail    = $detailString
                                       Status    = $status
                                       TimeStamp = [datetime]::UtcNow}}
            $instanceResults += New-AzStackHciResultObject @params
        }
        catch {
            throw $_
        }
    }
    return $instanceResults
}

function Test-PSModules
{
    <#
    .SYNOPSIS
        Test version of PS Modules installed is correct
    .DESCRIPTION
        Get installed PS module from local machine and validated its expected as per the current recipe.
        Expecting data from PsSessions to include local machine indicating ECE.
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    $sb = {
        Param($lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $vcNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.VersionControl.Operations"
        Import-Module "$vcNugetPath\content\Scripts\VersionControlHelpers.psm1" -Force -DisableNameChecking -Verbose:$false | Out-Null
        $InstalledModules = Get-InstalledPowerShellModules
        foreach ($mod in $InstalledModules) {
            # Get-InstalledPowerShellModules will return 0.0.0 if a required module is not installed
            # it is OK if the module is not yet installed because we are looking to update/install the modules
            # this is true especially if they are not currently installed or they have an older version installed.
            # however, if the customer has a newer version installed we do not want to downgrade the version
            # just because the recipe calls for an older version.
            try {
                $InstalledVersionObj = [Version]::Parse($mod.InstalledVersion)
            }
            catch {
                $InstalledVersionObj = $null
                $mod.InstalledVersion = "N/A"
            }
            try {
                $RequiredVersionObj = [Version]::Parse($mod.RequiredVersion)
            }
            catch {
                $RequiredVersionObj = $null
                $mod.RequiredVersion = "N/A"
            }
            if (-not $InstalledVersionObj -and -not $RequiredVersionObj) {
                $rc += 1
                $res = ${resultSeverity}
                $msg = $lswTxt.PSModulesFailedToParseBothVersions -f $mod.Name, $localHost, $mod.InstalledVersion, $mod.RequiredVersion
                $dtl = $msg
            } elseif (-not $InstalledVersionObj) {
                $rc += 1
                $res = ${resultSeverity}
                $msg = $lswTxt.PSModulesFailedToParseInstalledVersion -f $mod.Name, $localHost, $mod.InstalledVersion
                $dtl = $msg
            } elseif (-not $RequiredVersionObj) {
                $rc += 1
                $res = ${resultSeverity}
                $msg = $lswTxt.PSModulesFailedToParseRequiredVersion -f $mod.Name, $localHost, $mod.RequiredVersion
                $dtl = $msg
            } elseif ($InstalledVersionObj -le $RequiredVersionObj) {
                $res = 'SUCCESS'
                $msg = $lswTxt.PSModulesSuccess -f $mod.Name, $localHost, $mod.InstalledVersion, $mod.RequiredVersion
                $dtl = $lswTxt.PSModulesDetailSuccess -f $mod.Name, $mod.InstalledVersion
            } else {
                $rc += 1
                $res = ${resultSeverity}
                $msg = $lswTxt.PSModulesFailure -f $mod.Name, $localHost, $mod.InstalledVersion, $mod.RequiredVersion
                $dtl = $msg
            }
            $detailList.Add($dtl)
            $logLines.Add("${msg}|${res}")
        }
        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = if ($psSession) {
        Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$lswTxt, $resultSeverity)
    } else {
        Invoke-Command -ScriptBlock $sb -ArgumentList (,$lswTxt, $resultSeverity)
    }
    $splat = @{responses   = $responses
        Name        = "AzStackHci_ValidatedRecipe_PowerShellModule_Version"
        Title       = "Test PowerShell Module Version"
        DisplayName = "Test PowerShell Module Version"
        Severity    = $resultSeverity
        Description = "Validating that the PS modules installed on the host are the same versions defined in the validated recipe."}
    return (TestResult @splat)
}

function Test-AzCli
{
    <#
    .SYNOPSIS
        Validate that the installed version of Azure.CLI matched the required one
    .DESCRIPTION
        Get installed version using 'az version' and compare it to the required version defined by the VersionControl module
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    $sb = {
        Param($lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $vcNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.VersionControl.Operations"
        Import-Module "$vcNugetPath\content\Scripts\VersionControlHelpers.psm1" -Force -DisableNameChecking -Verbose:$false | Out-Null
        $name = 'AzCli'
        try {
            $installedVersion = (az version -o json |ConvertFrom-Json).'azure-cli'
        }
        catch {
            $installedVersion = "N/A"
        }
        try {
            $requiredVersion = (Get-ValidatedRecipe -TypesToInclude 'AzCli' -TagsToInclude 'Azure').RequiredVersion
        }
        catch {
            $requiredVersion = "N/A"
        }
        try {
            $InstalledVersionObj = [Version]::Parse($installedVersion)
        }
        catch {
            $InstalledVersionObj = $null
            $installedVersion = "N/A"
        }
        try {
            $RequiredVersionObj = [Version]::Parse($requiredVersion)
        }
        catch {
            $RequiredVersionObj = $null
            $requiredVersion = "N/A"
        }
        if (-not $InstalledVersionObj -and -not $RequiredVersionObj) {
            $rc += 1
            $res = ${resultSeverity}
            $msg = $lswTxt.AzCliFailedToParseBothVersions -f $name, $localHost, $installedVersion, $requiredVersion
            $dtl = $msg
        } elseif (-not $InstalledVersionObj) {
            $rc += 1
            $res = ${resultSeverity}
            $msg = $lswTxt.AzCliFailedToParseInstalledVersion -f $name, $localHost, $requiredVersion, $installedVersion
            $dtl = $msg
        } elseif (-not $RequiredVersionObj) {
            $rc += 1
            $res = ${resultSeverity}
            $msg = $lswTxt.AzCliFailedToParseRequiredVersion -f $name, $localHost, $requiredVersion
            $dtl = $msg
        } elseif ($InstalledVersionObj -eq $RequiredVersionObj) {
            $res = 'SUCCESS'
            $msg = $lswTxt.AzCliSuccess -f $name, $localHost, $installedVersion, $requiredVersion
            $dtl = $msg
        } else {
            $rc += 1
            $res = ${resultSeverity}
            $msg = $lswTxt.AzCliFailure -f $name, $localHost, $installedVersion, $requiredVersion
            $dtl = ''
            $dtl += $msg
            $dtl += " The Az-Cli version installed conflicts with updates; "
            $dtl += "Find the [Azure-Cli] msi file path in the C:/NugetStore and run: "
            $dtl += "(Start-Process msiexec.exe -Wait -ArgumentList (/I [fullPathToAzureCliMsiFile] /quiet))"
        }
        $detailList.Add($dtl)
        $logLines.Add("${msg}|${res}")

        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = if ($psSession) {
        Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$lswTxt, $resultSeverity)
    } else {
        Invoke-Command -ScriptBlock $sb -ArgumentList (,$lswTxt, $resultSeverity)
    }
    $splat = @{responses   = $responses
        Name        = "AzStackHci_ValidatedRecipe_AzureCli_Version"
        Title       = "Test Azure-Cli Module Version"
        DisplayName = "Test Azure-Cli Module Version"
        Severity    = $resultSeverity
        Description = "Validating that the installed version of Az.Cli is the required version."}
    return (TestResult @splat)
}

# This function should only be call during update scenarios health check. This is to avoid case where 2510 pnu was offered to 2508 build due to bug in VSR.
function Test-ServicesVersion
{
    <#
    .SYNOPSIS
        Validate that the services version is not 2508
    .DESCRIPTION
        This function checks if the services version is not 2508.
    #>

    $sb = {
        Param($lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $remediation = $null
        Import-Module ECEClient 3>$null 4>$null
        $stampInformation = Get-StampInformation
        if ("Upgrade" -ne $stampInformation.InstallationMethod) {
            # Not brownfield env, skip checking
            $msg = $lswTxt.ServicesVersionNotCheckedBrownfield -f $localHost
            $res = 'SUCCESS'
            $dtl = $msg
        } else {
            $servicesVersion = $stampInformation.ServicesVersion
            try {
                $servicesVersionObj = [Version]::Parse($servicesVersion)
            }
            catch {
                $servicesVersionObj = $null
            }

            # Check for parsing versions errors
            if (-not $servicesVersionObj) {
                $rc += 1
                $res = ${resultSeverity}
                $msg = $lswTxt.ServicesVersionFailedToParse -f $localHost, $servicesVersion
                $dtl = $msg
            } else {
                $servicesVersionMinor = $servicesVersionObj.Minor
                if ($servicesVersionMinor -eq 2508) {
                    # 2508 services version, not supported.
                    $rc += 1
                    $res = ${resultSeverity}
                    $msg = $lswTxt.ServicesVersionWrong -f $localHost, $servicesVersion
                    $dtl = ''
                    $dtl += $msg
                    $dtl += "The services version is not supported in this Azure Local Update. "
                    $dtl += "Please follow this TSG to correct the stamp version: https://aka.ms/azhci-tsg-stampversion"
                    $remediation = "Please follow this TSG to correct the stamp version: https://aka.ms/azhci-tsg-stampversion"
                } else {
                    # Valid scenario
                    $res = 'SUCCESS'
                    $msg = $lswTxt.ServicesVersionSuccess -f $localHost, $servicesVersion
                    $dtl = $msg
                }
            }
        }

        $detailList.Add($dtl)
        $logLines.Add("${msg}|${res}")

        $response = "" | Select-Object -Property rc, details, computername, logLines, remediation
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        $response.remediation = $remediation
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = Invoke-Command -ScriptBlock $sb -ArgumentList (,$lswTxt, $resultSeverity)
    $splat = @{responses   = $responses
        Name        = "AzStackHci_ValidatedRecipe_ServicesVersion"
        Title       = "Test Azure Local Services Version"
        DisplayName = "Test Azure Local Services Version"
        Severity    = $resultSeverity
        Description = "Validating that the services version is supported."
        Remediation = $responses.remediation
    }
    return (TestResult @splat)
}

Export-ModuleMember -Function Test-*
# SIG # Begin signature block
# MIInSQYJKoZIhvcNAQcCoIInOjCCJzYCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDvFs/+wmGArkBg
# amMHef5LwBQlDh9vsozj/ZOJdlgA+qCCDLowggX1MIID3aADAgECAhMzAAACHU0Z
# yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1
# OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD
# VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8
# o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg
# 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4
# Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R
# X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk
# ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B
# Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O
# BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw
# HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg
# UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0
# JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh
# MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy
# dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9
# s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H
# VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3
# w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n
# 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs
# A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo
# Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb
# SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6
# 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z
# V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v
# 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs
# /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA
# AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow
# VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo
# MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh
# emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h
# KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd
# M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp
# yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t
# Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5
# REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs
# 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK
# Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5
# pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW
# eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ
# 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC
# NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI
# MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4
# NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh
# ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q
# hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU
# nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb
# H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z
# uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u
# vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW
# 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV
# DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10
# 1cY2L4A7GTQG1h32HHAvfQESWP0xghnlMIIZ4QIBATBuMFcxCzAJBgNVBAYTAlVT
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv
# c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w
# DQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK
# KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGNCBjC8
# R89dMSy4FtEl8gHDu2te0ukENxIv9V7ScJYZMEIGCisGAQQBgjcCAQwxNDAyoBSA
# EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w
# DQYJKoZIhvcNAQEBBQAEggEALO0rBtustr76QmiCfVn+dv8nYlsgYkAVFaxQgd7i
# zmK9CZ7AVffnQVFMFN7WEaYPcF5cDJ2wVtDU7bKyY6VoJtrtOk7ak4fsyPJw3SkR
# Nh05qNqhb9uswDO12r4d9sQlC5hcqr40uPO2NDbAR467E0vvL/ui0EIFREWivdEP
# ZGI88pDSwl6vpjcqrtoSO95ztKD0qEcewRqHBWvDlEMrWnSkS9AOY69UtFxotoWy
# Xw20BZMleT8dv+1XDcMN62c3pAU1D7oWt0I8dn6JHoUNYcFA5ZgFAMcT7Se6tq6O
# 9O8CVaqJgBZfuT1z5fgIMTCCdJx+oNLAbGlsIs/TsEDjbqGCF5cwgheTBgorBgEE
# AYI3AwMBMYIXgzCCF38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUD
# BAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoD
# ATAxMA0GCWCGSAFlAwQCAQUABCBzP5WO+fCf1if81TqMnf9t48SJmfXQUUg/g/ne
# uwgq6AIGadfEjs1GGBMyMDI2MDUwMzE0MzExMC4zNDZaMASAAgH0oIHRpIHOMIHL
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxN
# aWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRT
# UyBFU046OTIwMC0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFNlcnZpY2WgghHtMIIHIDCCBQigAwIBAgITMwAAAiNP2WAkU8/+KwABAAAC
# IzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAe
# Fw0yNjAyMTkxOTM5NTdaFw0yNzA1MTcxOTM5NTdaMIHLMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj
# YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0wNUUw
# LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCK6Q2nk5WUdKzSCSafp+UjUARs
# WxHKS63rJhFC/zSabFumTBuaJ0QNrmqevub5Db7fSj5qtwwKnjIO92+HXF67192f
# ujL7DFot5WEj/AtEZ/XrzFHimKlN1h6gEQwP5I67wizaPW5ZzSBNpaLBg5oHvASP
# OZtwdNUoZ+DQKF3hJl1KZuoIlVK+qi7cLjgak6s5oOZcRCMrKnuC3aoVa6wRDbYv
# KUuj7rkFx9KO0PsHJ/k+LnZMggRheh4AVdawyh+oOzKPjlQGUNfSeWUgym2U9CLa
# 8tt0mQX4DxDz6+ram50gj1oAfyQ6TQ7r96PADFOKBgaU7+cpHnaZG89dTegQ6ydB
# RGIycOw1dRX2eKDRRzziK3cn0WaIm/7OeGsyQKjIzEQuUTDv0Jj/9zQ7truLOOpJ
# D98BJVOK7je84Sz2hb3HvUST7j1j2N8peD6olkpFHR/1Z8Jz4F+mkrUF7MmPAirY
# HRzunbIg3HrDMNwFYN7yBkDA4/VMo9CY0y9oGUoq2yjbCwTibz9VYl93nB3QQiTC
# T9nW3M+TOWB+PMrZpExq1BSHmKPzIqehKqrUDoM33PK+dEKwpYLET6uXq4HuQRMX
# WT//sPubUnQAaaUMfQhAZSy23HtxwtN3eK9+T4wCav2wQFt57eUOwUW5/DCzMF9t
# ua5He1hNvgcAXaiG1wIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFNbAh89v29nPY9bw
# Qb1QYCzxVgeXMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1Ud
# HwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js
# L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggr
# BgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw
# MTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgw
# DgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCHQwe7z5tp4NZwAf1c
# B+4c9J4svw3P6WqGBMxtqznS6DdzUzStXHCaPZhM41g1iKHNnmcnLjwLujOEaNjh
# SnUDiAZqQjW5ZapOBxgc7Egghh9k+r78qWAe3rJ4QohBbhSGdZtKivTRaeRqmnhy
# 8+ThrKhzCeEwaarXJimZwSpdQQUDbheWHeyAxASqultd5KO0m/UFvO03tfepqGXA
# 4tCg/WGECwKqOjJzpRAfPIB6y1HyVrk+vmL5rpEbTwwLOtX7WxFGG8+cYLk9HjaD
# kxraA/HYlKQRx1sdza+w/gulLwgOnByRJKF2rr8M7FNIlwoi6ywFpaNc8A7HewaG
# jgw/tfcE260I1XekGluANI9HnONOYWlI7BKBQbWE2teo6vsQ1Vg8B8rTZSePVdmX
# L1PPqqs3KVdFKM5kYocPCDM+6VL32IV96sESf2T7DjxanpCg2D2UYj4Z1i7cy8U1
# LLDGg55KWs4af2RRBjH2MulHgAmW5obKxiZCDQjRaroJ2XElXUhigE9BzvhCFbT/
# HDY2vpVpl5HnSpcCSxmL5i5lIT/xbAQMI7Luh75Xrm+IslfFWOGOGMlCp+24qEJE
# glXEP7xwsolNdBNndXihhyIefVGlI1DR7xGELiJrk8ifVWYo9XEbEXv/lbvp6F2R
# 2UsnweWckvq0y1HWnLHDqH6dPjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkA
# 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
# VTNYs6FwZvKhggNQMIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
# T3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjkyMDAtMDVFMC1E
# OTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEw
# BwYFKw4DAhoDFQA4RWFs+kTiZnoZiAj1BtYj8zCNaqCBgzCBgKR+MHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7aE6szAiGA8y
# MDI2MDUwMzAzMDgzNVoYDzIwMjYwNTA0MDMwODM1WjB3MD0GCisGAQQBhFkKBAEx
# LzAtMAoCBQDtoTqzAgEAMAoCAQACAgL2AgH/MAcCAQACAhJCMAoCBQDtoowzAgEA
# MDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAI
# AgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAK62FgK5K5XFuLoLHYR6IUW+ogJf
# KCdyOFrPfdTzuENPYK4pNJRhdSYBwZLnmbGbVkZDDTDbfoHKrru9aeR7Yz01YO+s
# KuJlnqXWr1KaCtN+3JD3UPXJVScMbMTniu6dkoWFw2h3TD8IvlxYAub419x2i42f
# Xee7sCLaYJYCyEYPuadY7XVpoveMOQeaaUy4CnFaXuxFE6H7kDC6MrIWN1G2mmkJ
# WNRsoKoA9Srs2ZfnRZuyX2F0j13KNLZ8Fy+lTnUATgAhEDJHsaKT+A2NJQumzsqy
# zINUNUVOO6KLYVQbO6Akb5xWoRsBzRw9dTPYNpAhbhqQtaoC3KGR899zWNsxggQN
# MIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAiNP
# 2WAkU8/+KwABAAACIzANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0G
# CyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDIniBvjVOKj9gacqr9XW9i/yhn
# xXd6cuFojwSqUENoUzCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIJbwMywR
# bvcGiynjnwjAqcaD47yYvebKZRAvtEAR5u6zMIGYMIGApH4wfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAIjT9lgJFPP/isAAQAAAiMwIgQgit078tPa
# fcQnnQptmE1wsN7QTb6Exag5U1V1RqiSX9owDQYJKoZIhvcNAQELBQAEggIAJcCf
# LgfGAD1SDy+SvDsTyP+XyiA8oaDsw/c5yJ9OklHcBO76SgUbIrWZg+W1x6dgKvD4
# 3NH4vQ/Y1aqkfaA0S66UhZnnmA+/fOzVipR1qRlIe8YkwN/heha+yY16W4ZbOJ3H
# 1d0OJnRx5+TNNt2wXnXGTbgGGD6U5gGmBLiSstHQqfCk8Tbk/qdXprQlHCiG/N9I
# 1GLSmkKeiJBXkAO4DFJRSIUuddNpJ/pfB5vsNB+ZHKH5XZaWh4Etu6nuOhRfONi6
# iG+iMd5FqePGY+yXjTITrwicbC5aw/YzzLGtH5BX4+fOny1MIL5axiFQVtBK/vlk
# 2Z6Fx3g9ecvUUjXVEC0DrEFHtankKKFuydrWpwnQa4nlP3DuFGbgp47ZBjlmRmbS
# gmLmm1RXKcQMn0ynwPjIgnqR4c7Nn0ERVKjOvHnILLYhFOIw8IZyuZxqQYEvsyxt
# /2P5LI96iaVBm8z1M1O75++9AK2BBY+cjqAJwTFC6Qq0myNAShtQyC23X7Ou5TBa
# nkVivKJyrSRzCM6NemwfZMlqQUsRjw7EpL8bZrynyoQVt8vDJRa85CsKqrovNJPw
# Tfg3WmLmK0r/Qck2SYaiRYDX7L4/8Kwd/9Er/hZ4Ihk8VATRTIs1guymGlX453J8
# DnW+HdlbleC2EDwt9Gu671/q/0dL5VLxfLLydiU=
# SIG # End signature block