AzStackHciObservability/AzStackHci.Observability.Helpers.psm1

Import-LocalizedData -BindingVariable lvsTxt -FileName AzStackHci.Observability.Strings.psd1

class HealthModel
{
    # Attributes for Azure Monitor schema
    [string]$Name #Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer.
    [string]$Title #User-facing name; one or more sentences indicating the direct issue.
    [string]$Severity #Severity of the result (Critical, Warning, Informational, Hidden) - this answers how important the result is. Critical is the only update-blocking severity.
    [string]$Description #Detailed overview of the issue and what impact the issue has on the stamp.
    [psobject]$Tags #Key-value pairs that allow grouping/filtering individual tests. For example, "Group": "ReadinessChecks", "UpdateType": "ClusterAware"
    [string]$Status #The status of the check running (i.e. Failed, Succeeded, In Progress) - this answers whether the check ran, and passed or failed.
    [string]$Remediation #Set of steps that can be taken to resolve the issue found.
    [string]$TargetResourceID #The unique identifier for the affected resource (such as a node or drive).
    [string]$TargetResourceName #The name of the affected resource.
    [string]$TargetResourceType #The type of resource being referred to (well-known set of nouns in infrastructure, aligning with Monitoring).
    [datetime]$Timestamp #The Time in which the HealthCheck was called.
    [psobject]$AdditionalData #Property bag of key value pairs for additional information.
    [string]$HealthCheckSource #The name of the services called for the HealthCheck (I.E. Test-AzureStack, Test-Cluster).
}

class AzStackHciObservabilityVolumeTarget : HealthModel
{
    # Attribute for performing check
    [string]$ExpectedObservabilityVolumeSize
    [string]$CurrentObservabilityVolumeSize
}

class AzStackHciObservabilityRemoteSupportTarget : HealthModel 
{
    #Additional Attribute
    [string]$RemoteSupportSessionEndTime
    [string]$RemoteSupportSessionStartTime
    [string]$RemoteSupportSessionNodeName
}

class AzStackHciObservabilityLogCollectionTarget : HealthModel {}

function Test-ObservabilityVolume
{
    <#
    .SYNOPSIS
        Check volume free space meets the required observability threshold size
    .DESCRIPTION
        Check volume free space meets the required observability threshold size
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for observability validation. e.g. Deployment, Update, etc
    .PARAMETER PhysicalDriveLetter
        Specify PhysicalDriveLetter used to validation observability Volume. Default C drive is used as observability volume.
    #>

    [CmdletBinding()]
    param (

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession,

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

        [Parameter(Mandatory=$false, ParameterSetName="DefaultSet")]
        [string] $PhysicalDriveLetter = "C"
    )

    try
    {
        $defalutHC4ObservabilityVolumeSize = 40
        $defalutVMObservabilityVolumeSize = 30
        Log-Info -Message ($lvsTxt.ObservabilityVolumeStartInfo) -Type Info
        Log-Info -Message ($lvsTxt.ObservabilityVolumeDriveInfo -f $PhysicalDriveLetter)
        $lowDiskMsg = ($lvsTxt.LowDiskSpaceMsg -f $PhysicalDriveLetter)

        # Scriptblock to test observabilityVolumeSize on each server
        $testVolumeSb = {
            $AdditionalData = @()
            $status = "Succeeded"
            $errorMsg = $null
            $hardwareType = $null
            $expectedObservabilityVolumeSizeInGB = $args[0]
            $freeSpaceInGB = 1

            try
            {
                # Check if env is Virtual
                $hardwareType = (Get-WmiObject -Class Win32_ComputerSystem).Model
                if ($hardwareType -eq "Virtual Machine")
                {
                    $expectedObservabilityVolumeSizeInGB = $args[1]
                }

                # Check free space on physical volume
                $totalFreeSpace = (Get-Volume -DriveLetter $args[2]).SizeRemaining
                $freeSpaceInGB = [int]($totalFreeSpace / 1GB)        
                if ($freeSpaceInGB -lt $expectedObservabilityVolumeSizeInGB)
                {
                    throw $args[3]
                }
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $status = "Failed"
            }
            finally
            {
                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    HardwareType  = $hardwareType
                    ExpectedObservabilityVolumeSize = $expectedObservabilityVolumeSizeInGB
                    CurrentObservabilityVolumeSize = $freeSpaceInGB
                    Status    = $status
                    Source    = $ENV:COMPUTERNAME
                    Resource  = "Observability volume '$($args[2])' needs, $($expectedObservabilityVolumeSizeInGB) GB free space."
                    Detail    = $errorMsg
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $observabilityVolumeSizeResult = Invoke-Command -Session $PsSession -ScriptBlock $testVolumeSb -ArgumentList $defalutHC4ObservabilityVolumeSize, $defalutVMObservabilityVolumeSize, $PhysicalDriveLetter, $lowDiskMsg
        

        # build result
        $now = Get-Date
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($observabilityVolumeSizeResult.Status -contains 'Succeeded') { 'Succeeded' } else { 'Failed' }

        $volumeResult = New-Object -Type AzStackHciObservabilityVolumeTarget -Property @{
            Name               = 'AzStackHci_Observability_Volume'
            Title              = 'Observability Volume Requirement'
            Severity           = 'Critical'
            Description        = 'Test to check observability volume size requirement is met'
            Tags               =  $OperationType
            Remediation        = 'Free up disk space for Observability Volume'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $observabilityVolumeSizeResult.HardwareType | Get-Unique 
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $observabilityVolumeSizeResult
            HealthCheckSource  = ((Get-PSCallStack)[-1].Command)
            ExpectedObservabilityVolumeSize = $observabilityVolumeSizeResult.ExpectedObservabilityVolumeSize
            CurrentObservabilityVolumeSize = $observabilityVolumeSizeResult.CurrentObservabilityVolumeSize
        }
        return $volumeResult
    }
    catch
    {
        throw $_
    }
}

function Test-LogCollection
{
    <#
    .SYNOPSIS
        Check log collection component mets observability condition for update\upgrade
    .DESCRIPTION
        Check if log collection is in progress, if yes then indicate result with Warning
    .PARAMETER PsSession
        Specify the PsSession used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for observability validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (

        [Parameter(Mandatory = $true)]
        $PsSession,

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

    try
    {
        Log-Info -Message ($lvsTxt.LogCollectiontInfo) -Type Info
        $logCollectiontErrMsg = $($lvsTxt.LogCollectiontInfo)

        # Scriptblock to check log collection status
        $testlogCollectionSb = {
            $AdditionalData = @()
            $status = 'Succeeded'
            $errorMsg = $null
            $hardwareType = $null
            $logCollectionTime = $null
            $logCollectionStatus = $null

            try
            {
                # Get HardwareType
                    $hardwareType = (Get-WmiObject -Class Win32_ComputerSystem).Model

                    # Check check log collection status
                    $logCollectionHistory = Get-LogCollectionHistory -Verbose:$false -ErrorAction SilentlyContinue
                    if ($logCollectionHistory -ne $null `
                         -and $logCollectionHistory[0] -ne $null `
                         -and $logCollectionHistory[0].Status -eq "Running")
                    {
                        $logCollectionTime = $logCollectionHistory[0].TimeCollected
                        $logCollectionStatus = $logCollectionHistory[0].Status 
                        $status = 'Failed' 
                        throw $args[0]
                    }
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $status = 'Failed'
            }
            finally
            {
                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    LogCollectionTime = $logCollectionTime
                    LogCollectionStatus = $logCollectionStatus
                    HardwareType  = $hardwareType
                    Status    = $status
                    Source    = $ENV:COMPUTERNAME
                    Resource  = "Log Collection in progress"
                    Detail    = $errorMsg    
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $logCollectionResult = Invoke-Command -Session $PsSession -ScriptBlock $testlogCollectionSb -ArgumentList $logCollectiontErrMsg

        # build result
        $now = Get-Date
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($logCollectionResult.Status -contains 'Succeeded') { 'Succeeded' } else { 'Failed' }

        $logCollectionResultSet = New-Object -Type AzStackHciObservabilityLogCollectionTarget -Property @{
            Name               = 'AzStackHci_Observability_LogCollection'
            Title              = 'Observability Log collection Requirement'
            Severity           = 'Warning'
            Description        = 'Test to check observability log collection requirement is met'
            Tags               =  $OperationType
            Remediation        = 'Stop or Wait for log collection to be completed'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $logCollectionResult.HardwareType | Get-Unique 
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $logCollectionResult
            HealthCheckSource  = ((Get-PSCallStack)[-1].Command)
        }
        return $logCollectionResultSet
    }
    catch
    {
        throw $_
    }
}

function Test-RemoteSupport
{
    <#
    .SYNOPSIS
        Test Remote Support Session Terminal is active
    .DESCRIPTION
        Test if active Remote Support Session is in progress
    .PARAMETER PsSession
        Specify the PsSession used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for observability validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (

        [Parameter(Mandatory = $true)]
        $PsSession,

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

    try
    {
        Log-Info -Message ($lvsTxt.RemoteSupportStartInfo) -Type Info
        $remoteSupportErrMsg = $($lvsTxt.RemoteSupportErrMsg)

        # Scriptblock to check remote support status
        $remoteSupportSessionSb = {
            $AdditionalData = @()
            $status = 'Succeeded'
            $errorMsg = $null
            $hardwareType = $null
            $nodeName  = $null
            $startTime = $null
            $endTime = $null

            try
            {
                # Get HardwareType information
                $hardwareType = (Get-WmiObject -Class Win32_ComputerSystem).Model

                # Check check remote support status
                $getRemoteSupportAccess = Get-RemoteSupportAccess -Verbose:$false -ErrorAction SilentlyContinue
                if ($getRemoteSupportAccess -ne $null)
                {
                    $remoteSupportSessionHistory = Get-RemoteSupportSessionHistory -Verbose:$false -ErrorAction SilentlyContinue | where { $_.EndTime -gt $(Get-Date) } 
                    if ($remoteSupportSessionHistory -ne $null -and $($remoteSupportSessionHistory.count) -gt 0)
                    {
                        $startTime = $remoteSupportSessionHistory.StartTime
                        $endTime = $remoteSupportSessionHistory.EndTime
                        $nodeName = $remoteSupportSessionHistory.NodeName
                        $status = 'Failed'
                        throw $args[0]
                    }
                }  
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $status = 'Failed'
            }
            finally
            {
                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    HardwareType  = $hardwareType
                    Status    = $status
                    RemoteSupportSessionEndTime  = $endTime
                    RemoteSupportSessionStartTime = $startTime
                    RemoteSupportSessionNodeName = $nodeName
                    Source = $ENV:COMPUTERNAME
                    Resource = "Open Remote Support Session terminal."
                    Detail = $errorMsg
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $remoteSupportSessionResult = Invoke-Command -Session $PsSession -ScriptBlock $remoteSupportSessionSb -ArgumentList $remoteSupportErrMsg


        # build result
        $now = Get-Date
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($remoteSupportSessionResult.Status -contains 'Succeeded') { 'Succeeded' } else { 'Failed' }

        $remoteSupportSessionSet = New-Object -Type AzStackHciObservabilityRemoteSupportTarget -Property @{
            Name               = 'AzStackHci_Observability_RemoteSupport'
            Title              = 'Observability RemoteSupport Requirement'
            Severity           = 'Critical'
            Description        = 'Test to check observability RemoteSupport requirement is met'
            Tags               =  $OperationType
            Remediation        = 'Active remote session terminal is open, please contact Microsoft Support.'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $remoteSupportSessionResult.HardwareType | Get-Unique
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $remoteSupportSessionResult
            RemoteSupportSessionEndTime  = $remoteSupportSessionResult.RemoteSupportSessionEndTime
            RemoteSupportSessionStartTime = $remoteSupportSessionResult.RemoteSupportSessionStartTime
            RemoteSupportSessionNodeName = $remoteSupportSessionResult.RemoteSupportSessionNodeName
            HealthCheckSource  = ((Get-PSCallStack)[-1].Command)
        }
        return $remoteSupportSessionSet
    }
    catch
    {
        throw $_
    }
}

Export-ModuleMember -Function Test-ObservabilityVolume
Export-ModuleMember -Function Test-LogCollection
Export-ModuleMember -Function Test-RemoteSupport
# SIG # Begin signature block
# MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB5XIz57BF9ejv2
# 8kSQogBy10XiniHKuazieURMCShBlKCCDXYwggX0MIID3KADAgECAhMzAAACy7d1
# OfsCcUI2AAAAAALLMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NTU5WhcNMjMwNTExMjA0NTU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC3sN0WcdGpGXPZIb5iNfFB0xZ8rnJvYnxD6Uf2BHXglpbTEfoe+mO//oLWkRxA
# wppditsSVOD0oglKbtnh9Wp2DARLcxbGaW4YanOWSB1LyLRpHnnQ5POlh2U5trg4
# 3gQjvlNZlQB3lL+zrPtbNvMA7E0Wkmo+Z6YFnsf7aek+KGzaGboAeFO4uKZjQXY5
# RmMzE70Bwaz7hvA05jDURdRKH0i/1yK96TDuP7JyRFLOvA3UXNWz00R9w7ppMDcN
# lXtrmbPigv3xE9FfpfmJRtiOZQKd73K72Wujmj6/Su3+DBTpOq7NgdntW2lJfX3X
# a6oe4F9Pk9xRhkwHsk7Ju9E/AgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUrg/nt/gj+BBLd1jZWYhok7v5/w4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzQ3MDUyODAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAJL5t6pVjIRlQ8j4dAFJ
# ZnMke3rRHeQDOPFxswM47HRvgQa2E1jea2aYiMk1WmdqWnYw1bal4IzRlSVf4czf
# zx2vjOIOiaGllW2ByHkfKApngOzJmAQ8F15xSHPRvNMmvpC3PFLvKMf3y5SyPJxh
# 922TTq0q5epJv1SgZDWlUlHL/Ex1nX8kzBRhHvc6D6F5la+oAO4A3o/ZC05OOgm4
# EJxZP9MqUi5iid2dw4Jg/HvtDpCcLj1GLIhCDaebKegajCJlMhhxnDXrGFLJfX8j
# 7k7LUvrZDsQniJZ3D66K+3SZTLhvwK7dMGVFuUUJUfDifrlCTjKG9mxsPDllfyck
# 4zGnRZv8Jw9RgE1zAghnU14L0vVUNOzi/4bE7wIsiRyIcCcVoXRneBA3n/frLXvd
# jDsbb2lpGu78+s1zbO5N0bhHWq4j5WMutrspBxEhqG2PSBjC5Ypi+jhtfu3+x76N
# mBvsyKuxx9+Hm/ALnlzKxr4KyMR3/z4IRMzA1QyppNk65Ui+jB14g+w4vole33M1
# pVqVckrmSebUkmjnCshCiH12IFgHZF7gRwE4YZrJ7QjxZeoZqHaKsQLRMp653beB
# fHfeva9zJPhBSdVcCW7x9q0c2HVPLJHX9YCUU714I+qtLpDGrdbZxD9mikPqL/To
# /1lDZ0ch8FtePhME7houuoPcMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ4wghmaAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIOBNSHfZ0SCfdgYn9P52iDxi
# VxWGACbYUGYnpDl/XvRvMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAGyozLDxYuiU04Q//lUD+IiYpTDZismVqifggvocJ4X0i8LgVQS6c60k5
# 4a4xJeNWe+3EnrlaYRTcPA7wHwBPyidhs54uo0Q+66GX6aZLevQxVKeIlPUnJl/T
# lVBv2gYlWxn2YBoJQb7BBpg6u9yro7cgafvHLxi/sSI57cKgFEYrBmkJzpBRuM9N
# R+bKxiqkrGAEyaYH2op/1YQy8f0EufgHgEsSu28m5ySqmzCYtQal5pSOFIa2EoMh
# RVDAIrwd0+Ue1dE/uPphIhW+jqA3pOEU/2wwa6E4qHeLwnLAuSXjUoxl+3LN3NRS
# luFw0mM1L+SJIX0SURMjhyTcE1pneKGCFygwghckBgorBgEEAYI3AwMBMYIXFDCC
# FxAGCSqGSIb3DQEHAqCCFwEwghb9AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFYBgsq
# hkiG9w0BCRABBKCCAUcEggFDMIIBPwIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCD2IjMGGUkNOneZcjy3YTpzk38IaHWcngaCufAd6PdOjwIGY3TU9J0R
# GBIyMDIyMTIwNzA3MTIxMC40MVowBIACAfSggdikgdUwgdIxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVs
# YW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046
# MDg0Mi00QkU2LUMyOUExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2WgghF4MIIHJzCCBQ+gAwIBAgITMwAAAbJuQAN/bqmUkgABAAABsjANBgkq
# hkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMjA5
# MjAyMDIyMDFaFw0yMzEyMTQyMDIyMDFaMIHSMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVy
# YXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjA4NDItNEJF
# Ni1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyqJlMh17+VDisL4GaXl/9a6r
# /EpPGt9sbbceh+ZD6pkA3gbI7vc8XfL04B+m3tB/aNyV1Y4ZQH4fMG7CWVjI/d/H
# gxjzO+4C4HfsW+jK2c0LYMqdWtWUc5VwZQv0KeaEM0wDb+eySMh/YiiIb0nSotiv
# x268d1An0uLY+r2C7JJv2a9QvrSiCyUI72CSHoWIQPAyvBSvxaNrqMWlROfLy2DQ
# 3RycI3bDh8qSnmplxtRgViJwtJv/oDukcK1frGeOrCGYmiJve+QonJXFu4UtGFVf
# Ef3lvQsd42GJ+feO+jaP7/hBXXSMSldVb6IL0GxO1Hr3G9ONTnVmA/sFHhgMRars
# mzKVI6/kHlMdMNdF/XzhRHMWFPJvw5lApjuaoyHtzwnzDWwQzhcNQXZRk3Lzb01U
# LMba190RdlofEXxGbGlBgHHKFnBjWui24hL6B83Z6r6GQBPeKkafz8qYPAO3MBud
# +5eMCmB5mrCBxgnykMn7L/FTqi7MnPUG97lNOKGSIDvBCxB7pHrRmT10903PDQwr
# meJHO5BkC3gYj3oWGOGVRZxRk4KS/8lcz84a7+uBKmVjB2Y8vPN8O1fK7L8YJTkj
# iXTyDqKJ9fKkyChiSRx44ADPi/HXHQE6dlZ8jd9LCo1S+g3udxNP4wHhWm9/VAGm
# mMEBBS6+6Lp4IbQwJU0CAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSZ8ieAXNkRmU+S
# MM5WW4FIMNpqcTAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV
# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny
# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI
# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMI
# MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA3Ee27cXMhptoNtaq
# zB0oGUCEpdEI37kJIyK/ZNhriLZC5Yib732mLACEOEAN9uqivXPIuL3ljoZCe8hZ
# SB14LugvVm1nJ73bNgr4Qh/BhmaFL4IfiKd8DNS+xwdkXfCWslR89QgMZU/SUJhW
# x72aC68bR2qRjhrJA8Qc68m5uBllo52D83x0id3p8Z45z7QOgbMH4uJ45snZDQC0
# S3dc3eJfwKnr51lNfzHAT8u+FHA+lv/6cqyE7tNW696fB1PCoH8tPoI09oSXAV4r
# EqupFM8xsd6D6L4qcEt/CaERewyDazVBfskjF+9P3qZ3R6IyOIwQ7bYts7OYsw13
# csg2jACdEEAm1f7f97f3QH2wwYwen5rVX6GCzrYCikGXSn/TSWLfQM3nARDkh/fl
# mTtv9PqkTHqslQNgK2LvMJuKSMpNqcGc5z33MYyV6Plf58L+TkTFQKs6zf9XMZEJ
# m3ku9VBJ1aqr9AzNMSaKbixvMBIr2KYSSM21lnK8LUKxRwPW+gWS2V3iYoyMT64M
# RXch10P4OtGT3idXM09K5ld7B9U6dcdJ6obvEzdXt+XZovi/U6Evb4nA7VPHcHSK
# s7U72ps10mTfnlue13VFJUqAzbYoUEeegvsmzulGEGJoqZVNAag5v6PVBrur5yLE
# ajjxWH2TfkEOwlL8MuhcVI8OXiYwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZ
# AAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVa
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1
# V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9
# alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmv
# Haus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928
# jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3t
# pK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEe
# HT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26o
# ElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4C
# vEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ug
# poMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXps
# xREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0C
# AwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYE
# FCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtT
# NRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNo
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5o
# dG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBD
# AEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZW
# y4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5t
# aWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAt
# MDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0y
# My5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pc
# FLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpT
# Td2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0j
# VOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3
# +SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmR
# sqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSw
# ethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5b
# RAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmx
# aQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsX
# HRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0
# W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0
# HVUzWLOhcGbyoYIC1DCCAj0CAQEwggEAoYHYpIHVMIHSMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFu
# ZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjA4
# NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
# aWNloiMKAQEwBwYFKw4DAhoDFQCOEn4R7JJF+fYoI2yOf1wX0BRJOqCBgzCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA
# 5zpZsTAiGA8yMDIyMTIwNzA4MTU0NVoYDzIwMjIxMjA4MDgxNTQ1WjB0MDoGCisG
# AQQBhFkKBAExLDAqMAoCBQDnOlmxAgEAMAcCAQACAhW/MAcCAQACAhJZMAoCBQDn
# O6sxAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMH
# oSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAmnrMpDym4BuisffkARzA
# blB+/e3qqTyL9P5VH+SvHqiv2aqe1GWJKF0RAhi44mDzCT95950Z3c2BTerb2cX4
# 1gfVwZFJkrWOk6caby4nv/WqVZFW3oysN+LScjquVQmEBByvvy27T5UfGHFEx52K
# OrNDQgdmFnvMohHdl9MF5xwxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
# dGFtcCBQQ0EgMjAxMAITMwAAAbJuQAN/bqmUkgABAAABsjANBglghkgBZQMEAgEF
# AKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEi
# BCAPIZGAoZj6cBTPNjqsmQK0uoq/3jkwsdZESFjhMAYzLTCB+gYLKoZIhvcNAQkQ
# Ai8xgeowgecwgeQwgb0EIFN4zjzn4T63g8RWJ5SgUpfs9XIuj+fO76G0k8IbTj41
# MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm
# MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGybkAD
# f26plJIAAQAAAbIwIgQggpVKJ9PLL+sdKrW8o0V5YlR/5V7hLD+vBXPMuyh82H0w
# DQYJKoZIhvcNAQELBQAEggIAMC+qCihm7KvMfQC/mic4ATGa3n6LcV/CRMWKeYub
# Tj5QewQ5AwnBQfKTF0RkDAqtDMPiOKVto+6baZYC7aJr7CFD/2CjP1UW1OCmhQ04
# sLWYULoI+cL08AL/dPkigszWIfn0pT/Ls6U3ws7KLK8SBmoODoejFa5rYP5WAu5O
# GwzZc9yosESv1f1dVs+D6JKAp1JHhS8PO9L+dF9VvZL0uc+P4bu+tiMW457DisZy
# 88rvP30Cf70OUnL3TI9FTB3VLz+BMGfEE1t/Du0qa2P0+tzd6OwCUGbmfN2wkdhh
# YmJFfEAzIJNjs+pQXRFrrbEnHN9v0659J/cGJBv+jGO0IQZbFhbOXt+rDu8bkprR
# H1bn0PKXEZvQB2hqWB2iObMTjVzb3avyYVyVbZYNT9ebjXYUf/C2XEHqJZEki7is
# Dpax16nyLqqj9xOgFT39i/XPUeqNQ1ejURnxA/NhSswMR0SlQzdnCkiU9Kf3wHvS
# QhvIk0NkpvSFDbx0NPGqHddOA6srkpdyfNdoRJ26egNH8Q3vPkJ84rzVaP9s2Rn6
# yFTwjik1JAs/x+PWJhQVFT5XT2VO8oHkzNz8bb5zae0959Mt14A3dO7J0W5WFZb2
# gahZCqdBKL3RlEOLJk2blBho91KM2JSMoppOXna6/LAkgp4mAnj5a5eue2hjxyN1
# UCw=
# SIG # End signature block