AzStackHCIARBStack/AzStackHci.ARBStack.Helpers.psm1

Import-LocalizedData -BindingVariable lARBSTxt -FileName AzStackHci.ARBStack.Strings.psd1

function Test-ARBStackBandwidth
{
    <#
    .SYNOPSIS
        Verify that the bandwidth is enough to download ARB Canary Image.
    .DESCRIPTION
        Verify that the bandwidth is enough to download ARB Canary Image.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for ARBStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string[]]
        $OperationType
    )

    try
    {
        Log-Info -Message ($lARBSTxt.BandwidthInfo) -Type Info

        # Import DownloadSdk module for bandwidth test
        Log-Info -Message ($lARBSTxt.ImportInfo -f "DownloadSdk") -Type Info
        Import-Module DownloadSdk -Verbose:$false -DisableNameChecking

        $severity = 'WARNING' # or 'CRITICAL' or 'INFORMATIONAL'
        $status = "SUCCESS"
        $nodeName = $ENV:COMPUTERNAME

        # DownloadSDK test params
        $timeout = 320
        $downloadSDKTimeout = "$($timeout)s"
        $canaryImageVersion = "0.1.37.10128"

        $elapsedTime = [System.Diagnostics.Stopwatch]::StartNew()

        try {
            Get-DownloadSdkRelease -CatalogName arc-appliance-stable-catalogs-ext -Name mariner-canary-vhdx-rpm-stable -Version $canaryImageVersion -Audience stable -Destination . -Parts 10 -Timeout $downloadSDKTimeout -HTTPS: $true -Verbose | Out-Null

            $detail = ($lARBSTxt.BandwidthSuccess -f $nodeName, $elapsedTime.Elapsed.Seconds, $timeout)
        }
        catch {
            $status = "FAILURE"
            $detail = ($lARBSTxt.BandwidthFail -f $nodeName, $elapsedTime.Elapsed.Seconds, $timeout)
            Log-Info -Message ($_.Exception.Message) -Type Error
            Log-Info -Message $detail -Type Error
        }
        finally {
            $elapsedTime.Stop()
        }

        $additionalData = @{
            Status    = $status
            Source    = $nodeName
            Resource  = $nodeName
            Detail    = $detail
        }

        $params = @{
            Name               = 'AzStackHci_ARBStack_ARBStackBandwidth'
            Title              = 'ARBStack Cluster Bandwidth'
            DisplayName        = 'ARBStack Cluster Bandwidth'
            Severity           = $severity
            Description        = "Test to check if ARB canary image can be downloaded in $timeout seconds"
            Tags               = @{
                OperationType = $operationType
            }
            Remediation        = 'Confirm that the network bandwidth is sufficient and performing optimally'
            TargetResourceID   = $nodeName
            TargetResourceName = $nodeName
            TargetResourceType = 'Node'
            Timestamp          = [datetime]::UtcNow
            Status             = $status
            AdditionalData     = $additionalData
            HealthCheckSource  = $ENV:EnvChkrId
        }

        $instanceResults = New-AzStackHciResultObject @params
        return $instanceResults
    }
    catch
    {
        throw $_
    }
}

function Test-AzCLIExtensionsOutsideVR
{
    <#
    .SYNOPSIS
        Verify that known incompatible Azure CLI extensions currently installed are detected.
    .DESCRIPTION
        Verify that known incompatible Azure CLI extensions currently installed are detected.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for ARBStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession,

        [Parameter()]
        [string]
        $scenario,

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

    try
    {
        $severity = 'INFORMATIONAL' # or 'CRITICAL' or 'WARNING'

        # Import helper modules
        $azureStackNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Solution.Deploy.CloudDeployment"
        $roleHelpersPath = Join-Path $azureStackNugetPath "content\Roles\Common\RoleHelpers.psm1"
        Log-Info -Message ($lARBSTxt.ImportInfo -f $roleHelpersPath) -Type Info
        Import-Module $roleHelpersPath -Force -DisableNameChecking -Verbose:$false | Out-Null

        $mocArblifeCycleNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.MocArb.LifeCycle"
        $mocArblifeCycleHelperPath = Join-Path $mocArblifeCycleNugetPath "Content\Scripts\MocArbHelper.psm1"
        Log-Info -Message ($lARBSTxt.ImportInfo -f $mocArblifeCycleHelperPath) -Type Info
        Import-Module $mocArblifeCycleHelperPath -Force -DisableNameChecking -Verbose:$false | Out-Null

        # Get scenario and validated recipe json path
        $validatedRecipeJson = Get-ValidatedRecipeJsonPath -Scenario $scenario
        Log-Info -Message ($lARBSTxt.ImportInfo -f $validatedRecipeJson) -Type Info

        # Get supported extensions from validated recipe json
        $modulesInVR = Get-Content -Path $validatedRecipeJson -Raw | ConvertFrom-Json
        $supportedAzCliExtensions = $modulesInVR | Where-Object { $_.Type -eq 'AzCliExtension' }
        $supportedAzCliExtensionsList = $supportedAzCliExtensions.Name | ForEach-Object { $_.Split('.')[-1].ToLower() }
        $supportedAzCliExtensionsNames = $supportedAzCliExtensionsList -join ", "
        Log-Info -Message ($lARBSTxt.AzCLIExtensionsSupported -f $supportedAzCliExtensionsNames) -Type Info

        # Scriptblock to collect extension data from nodes
        $scriptblock = {
            $nodeName = $ENV:COMPUTERNAME
            try{
                $currentlyInstalledExtensions = az extension list --output json --only-show-errors 2>$null | ConvertFrom-Json -ErrorAction SilentlyContinue
                if ($null -eq $currentlyInstalledExtensions) {
                    $currentlyInstalledExtensions = @()
                }
                return @{
                    NodeName = $nodeName
                    Extensions = $currentlyInstalledExtensions
                }
            }
            catch{
                return @{
                    NodeName = $nodeName
                    Extensions = @()
                }
            }
        }

        # Collect data from nodes
        if ($PsSession) {
            $nodeData = Invoke-Command -Session $PsSession -ScriptBlock $scriptblock
        }
        else {
            $nodeData = @(& $scriptblock)
        }

        $results = @()

        # Process data for each node locally
        foreach ($node in $nodeData) {
            $status = "SUCCESS"
            $nodeName = $node.NodeName
            $currentlyInstalledExtensions = $node.Extensions
            $remediation = 'https://learn.microsoft.com/en-us/cli/azure/azure-cli-extensions-overview'
            Log-Info -Message ($lARBSTxt.AzCLIExtensionsInfo -f $nodeName) -Type Info

            $extensionNames = if ($currentlyInstalledExtensions) { ($currentlyInstalledExtensions.Name -join ", ") } else { "None" }
            Log-Info -Message ($lARBSTxt.AzCLIExtensionsCurrent -f $extensionNames) -Type Info

            # Compare lists and report any unsupported extensions found
            $unsupportedExtensionsInstalled = @()
            if ($null -ne $currentlyInstalledExtensions -and $currentlyInstalledExtensions.Count -gt 0) {
                foreach ($extension in $currentlyInstalledExtensions) {
                    if (-not ($supportedAzCliExtensionsList -contains $extension.Name.ToLower())) {
                        $unsupportedExtensionsInstalled += $extension.Name
                    }
                }
            }

            if ($unsupportedExtensionsInstalled.Count -gt 0) {
                $unsupportedExtensionsInstalledNames = $unsupportedExtensionsInstalled -join ", "
                $status = "FAILURE"
                $detail = ($lARBSTxt.AzCLIExtensionsFail -f $unsupportedExtensionsInstalledNames, $supportedAzCliExtensionsNames, $extensionNames)
                $remediation = "Please remove unsupported Azure CLI extensions to ensure compatibility ($unsupportedExtensionsInstalledNames)."
                Log-Info -Message $detail -Type Error
            }
            else {
                $detail = ($lARBSTxt.AzCLIExtensionsSuccess -f $extensionNames)
                Log-Info -Message $detail -Type Info
            }

            $additionalData = @{
                Status    = $status
                Source    = $nodeName
                Resource  = $nodeName
                Detail    = $detail
            }

            $params = @{
                Name               = 'AzStackHci_ARBStack_AzCLIKnownIncompatibleExtensions'
                Title              = "ARBStack Azure CLI Known Incompatible Extensions for $nodeName"
                DisplayName        = "ARBStack Azure CLI Known Incompatible Extensions for $nodeName"
                Severity           = $severity
                Description        = "Detect known incompatible Azure CLI extensions that should be removed."
                Tags               = @{
                    OperationType = $OperationType
                }
                Remediation        = $remediation
                TargetResourceID   = $nodeName
                TargetResourceName = $nodeName
                TargetResourceType = 'Node'
                Timestamp          = [datetime]::UtcNow
                Status             = $status
                AdditionalData     = $additionalData
                HealthCheckSource  = $ENV:EnvChkrId
            }

            $instanceResults = New-AzStackHciResultObject @params
            $results += $instanceResults
        }

        return $results
    }
    catch
    {
        throw $_
    }
}

function Test-ARBIsNotLocked
{
    <#
    .SYNOPSIS
        Verify that the ARB is not locked.
    .DESCRIPTION
        Verify that the ARB is not locked.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for ARBStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Azure Environment used for Azure Login")]
        [string]
        $EnvironmentName,

        [Parameter(Mandatory = $true, HelpMessage = "Azure Tenant used for Azure Login")]
        [string]
        $TenantId,

        [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription used for Azure Login")]
        [string]
        $SubscriptionId,

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

    try
    {
        $severity = "INFORMATIONAL" # or "WARNING" or "CRITICAL"
        $status = "SUCCESS"
        $nodeName = $ENV:COMPUTERNAME
        $commandName = $MyInvocation.MyCommand
        $global:actionplanid = New-Guid
        $remediation = "https://github.com/Azure/AzureLocal-Supportability/blob/main/TSG/Update/UpgradeArbAndExtensions_fails_ApplianceResourceScopeLocked.md"

        # Login to Azure
        try {
            Log-Info -Message ($lARBSTxt.LoginInfo) -Type Info

            $mocArblifeCycleNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.MocArb.LifeCycle"
            $mocArblifeCycleHelperPath = Join-Path $mocArblifeCycleNugetPath "Content\Scripts\MocArbHelper.psm1"
            Log-Info -Message ($lARBSTxt.ImportInfo -f $mocArblifeCycleHelperPath) -Type Info
            Import-Module $mocArblifeCycleHelperPath -Force -DisableNameChecking -Verbose:$false | Out-Null

            Login-AzCliSessionWithMSI -subcriptionId $SubscriptionId -ActionPlanprefix $commandName -CloudName $EnvironmentName | Out-Null

            Log-Info -Message ($lARBSTxt.LoginSuccess) -Type Info
        }
        catch {
            $status = "FAILURE"
            $detail = ($lARBSTxt.LoginFail -f $_.Exception.Message)
            Log-Info -Message $detail -Type Error
            $remediation = "https://github.com/Azure/AzureLocal-Supportability/blob/main/TSG/EnvironmentValidator/Troubleshooting-MSI-Does-Not-Have-Access-To-Subscription.md"
        }

        # Check if ARB is locked only if login was successful
        try {
            if ($status -eq "SUCCESS") {
                Log-Info -Message ($lARBSTxt.ARBLockStatusInfo) -Type Info

                # Import archci module
                $archcimodule = Get-InstalledModule archci
                $archciPsm1Path = Join-Path $archcimodule.InstalledLocation -ChildPath "archci.psm1"
                Log-Info -Message ($lARBSTxt.ImportInfo -f $archciPsm1Path) -Type Info
                Import-Module $archciPsm1Path -Force -DisableNameChecking -Verbose:$false | Out-Null

                # Get ARB name and RG
                $arc = Get-ArcHciMgmtWithTelemetry -ActionPlanStep $commandName
                $arbId = $arc.ResourceBridge.Id
                $arbRG = $arc.ResourceBridge.resourceGroup
                $arbName = $arc.ResourceBridge.Name

                $listLocksArgs = "lock list --resource `"$arbName`" --resource-type `"Microsoft.ResourceConnector/appliances`" --resource-group `"$arbRG`" --only-show-errors"
                $locks = Invoke-ArcHciAzCommand -arguments $listLocksArgs | ConvertFrom-Json

                if($locks -eq $null -or $locks.Count -eq 0) {
                    $detail = $lARBSTxt.ARBLockStatusSuccess
                }
                else {
                    $detail = ($lARBSTxt.ARBLockStatusFail -f $locks.Count, $arbName)
                    throw $detail
                }
            }
        }
        catch {
            $status = "FAILURE"
            Log-Info -Message ($_.Exception.Message) -Type Error
            $detail = $_.Exception.Message
        }
        finally {
            Logout-AzCliSession
        }


        $additionalData = @{
            Status    = $status
            Source    = $nodeName
            Resource  = "Azure Resource Bridge"
            Detail    = $detail
        }

        $params = @{
            Name               = "AzStackHci_ARBStack_ARBIsLocked"
            Title              = "ARB should not be Locked"
            DisplayName        = "ARB should not be Locked"
            Severity           = $severity
            Description        = "Test to confirm that Azure Resource Bridge is not locked"
            Tags               = @{
                OperationType = $operationType
            }
            Remediation        = $remediation
            TargetResourceID   = $arbId
            TargetResourceName = $arbName
            TargetResourceType = "Azure Resource Bridge"
            Timestamp          = [datetime]::UtcNow
            Status             = $status
            AdditionalData     = $additionalData
            HealthCheckSource  = $ENV:EnvChkrId
        }

        $instanceResults = New-AzStackHciResultObject @params
        return $instanceResults
    }
    catch
    {
        throw $_
    }
}
# SIG # Begin signature block
# MIInSQYJKoZIhvcNAQcCoIInOjCCJzYCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBH/g5RoH42tVKb
# W43E77tBNZuylp9BmI2DHgpNmJZSRKCCDLowggX1MIID3aADAgECAhMzAAACHU0Z
# 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
# KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIL6cu9UI
# aHt3S8LUkdBXQx7+UosfLCOJQ7o5Q7fQR6HpMEIGCisGAQQBgjcCAQwxNDAyoBSA
# EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w
# DQYJKoZIhvcNAQEBBQAEggEAZAE4dsgXG5yZ1Y79ofm+CJDi5PIuso/eIK3vuFkC
# S8g3K8uXafbbtTyPn9ZwAZj6DU5ltER3TonzSci3EHfWhGV5n0hcVMVi+vs0NkSp
# Ampb3wfse8SGWt+tt2vUui/ED0ZXYEYV32wPNHl1w0V0kvv2e+CafDAfaOjR7u4l
# vwA5kCTcrT//Y2mX8POh8qQkLU7/I0vzb7fCUaRIH03VQaIIeg/rFM9JeyiYBJ3i
# gbDgVszfiJxf3Yfga7iXIEpJVsatL53NszIwSOTg0Hu7y+h8Tmqo7l50e3YgRS/H
# l5HvH9vK9Rjx2brgmW7VB7r5Oh154GYXtA2hpYyHxMt5MqGCF5cwgheTBgorBgEE
# AYI3AwMBMYIXgzCCF38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUD
# BAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoD
# ATAxMA0GCWCGSAFlAwQCAQUABCCs5GhhbLL0Pt+YlaCbBp551Y/C8LTHYN6nbBj1
# jJY62QIGadfEjs1DGBMyMDI2MDUwMzE0MzExMC4xMTZaMASAAgH0oIHRpIHOMIHL
# 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
# CyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCAfPqQDXKxAG08exNFEd8o+nCnp
# PiEC8aKd2g14A+KlxzCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIJbwMywR
# bvcGiynjnwjAqcaD47yYvebKZRAvtEAR5u6zMIGYMIGApH4wfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAIjT9lgJFPP/isAAQAAAiMwIgQgit078tPa
# fcQnnQptmE1wsN7QTb6Exag5U1V1RqiSX9owDQYJKoZIhvcNAQELBQAEggIAXs0f
# IdhgYOHmK3+R42F+p2WN6exYR0YxF23/7a9a9cQW4CmNOB6jXI3klS+GQLCTH8XA
# v7iRW/xRZK3WUecZPowU6CjBPiFwb9Qx/ffBGbrHREGCuPh1c3uRF5fph3r8uO27
# PIFtaGccDiMYPOHrEt9L6W84qCoa+dRoD9pL8p2ii1AIDvn2SA+bpnZOba+P3Xkh
# jm8cE8o9KABv8bY6pjwj1fm8n5HiiNdd0kS/9rX3cFaXEgc59BTD8nM4/A+dcGQC
# BakbHLrgtxPwU1v0vI2EBPkmwQTfCCBOo9wVDm8aKeunWkBDq19Czzs+NYzTbqeE
# 60xv/5Ydqh6rXk3rLwjitfvIhSN+5FGmpgXUq0ne8i20mZDHsq2AqIjxzdTEVw24
# r6NTkWPrk1RAjGLJwjCIAHsYXPaB0yYROi7e3e3CYPb3ybb9JfbmoxX59AA+C8zK
# ZpKv4OjCTSVSHxbFfPCqrWQD6GyYov3x8dz91Ve6nAhdnqpW1FUZKHjLqWzk3Sxz
# Mtl+VRNkq0DeujK98C8LMXm1U3LJGpZxY+04gPftpfjsO88UDddoTPuNgXHMINI1
# jNdCnG22XDUoH2693cwOldCzOqZEdASYG5Jdyb3lw4ZrwOuWfS8PEZqF6pGRLCK6
# kATe48FKKn0wmTwShV7MazcjRElw0IuhvrRopzI=
# SIG # End signature block