AzStackHciAddNode/AzStackHci.AddNode.Helpers.psm1

Import-LocalizedData -BindingVariable lanTxt -FileName AzStackHci.AddNode.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 AzStackHciAddNodeTarget : HealthModel {}

function Test-ADCredential {
    [CmdletBinding()]
    param (
        [pscredential]
        $ActiveDirectoryCredential
    )
    try
    {
        $adModuleExists = [bool](Get-Module ActiveDirectory -ListAvailable -ErrorAction SilentlyContinue)
        if ($adModuleExists)
        {
            [bool]$adTest = QueryAD -ActiveDirectoryCredential $ActiveDirectoryCredential
            $detail = $lanTxt.TestAD -f $AdTest, $true
            if ($adTest)
            {
                $status = 'Succeeded'
                Log-Info $detail
            }
            else
            {
                $status = 'Failed'
                Log-Info $detail -Type Warning
            }
        }
        else
        {
            $status = 'Failed'
            $detail = $lanTxt.NoADModule
            Log-info $detail -Type Warning
        }

        $instanceResult = New-Object AzStackHciAddNodeTarget
        $instanceResult.Name = 'AzStackHci_AddNode_AD_Credential_Check'
        $instanceResult.Title = 'Test AD Credential'
        $instanceResult.Severity = 'Critical'
        $instanceResult.Description = "Checking AD Credential is valid"
        $instanceResult.Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-prerequisites'
        $instanceResult.TargetResourceID = 'AD Credential'
        $instanceResult.TargetResourceName = 'AD Credential'
        $instanceResult.TargetResourceType = 'Credential'
        $instanceResult.Timestamp = [datetime]::UtcNow
        $instanceResult.HealthCheckSource = $ENV:EnvChkrId
        $AdditionalData = @()
        $AdditionalData += New-Object -TypeName PSObject -Property @{
            Source    = $ENV:COMPUTERNAME
            Resource  = 'AD Credential'
            Detail    = $detail
            Status    = $status
            TimeStamp = [datetime]::UtcNow
        }
        $instanceResult.AdditionalData = $AdditionalData
        $instanceResult.Status = if ($AdditionalData.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }
        $instanceResult
    }
    catch
    {
        throw ("Error testing AD credential: {0}" -f $_.Exception)
    }
}

function QueryAD {
    [CmdletBinding()]
    param (
        [pscredential]
        $ActiveDirectoryCredential
    )
    try
    {
        $domain = $ActiveDirectoryCredential.GetNetworkCredential().domain
        if ([string]::IsNullOrEmpty($domain)) {
            if (($ActiveDirectoryCredential.UserName -split '@').count -le 1)
            {
                throw "Credential should contain domain"
            }
            else
            {
                $domain = ($ActiveDirectoryCredential.UserName -split '@')[1]
            }
        }
        return [bool](Get-ADDomain -Credential $ActiveDirectoryCredential -Server $domain -ErrorAction SilentlyContinue)
    }
    catch
    {
        throw ("Error retrieving AD Object: {0}" -f $_.Exception)
    }
}

function Test-LocalCredential {
    [CmdletBinding()]
    param (
        [pscredential]
        $LocalCredential,

        [string]
        $ComputerName
    )
    try
    {
        $PsSession = Microsoft.PowerShell.Core\New-PsSession -ComputerName $ComputerName -Credential $LocalCredential
        Install-UtilityModule -PsSession $PsSession -CmdletName Test-Elevation
        $IsAdmin = {
            Import-Module -Name AzStackHci.EnvironmentChecker -Force -ErrorAction SilentlyContinue -Scope Local
            if (Get-Command -Name Test-Elevation -ErrorAction SilentlyContinue)
            {
                Test-Elevation
            }
            else
            {
                throw "Cannot find Test-Elevation function"
            }
        }
        $result  = [bool](Microsoft.PowerShell.Core\Invoke-Command -Session $PsSession -ScriptBlock $IsAdmin -ErrorAction SilentlyContinue)
        $detail = $lanTxt.TestLocalCredential -f $LocalCredential.UserName, $computerName, $result, $true
        if ($result)
        {
            $status = 'Succeeded'
            Log-Info $detail
        }
        else
        {
            $status = 'Failed'
            Log-Info $detail -Type Warning
        }
        $instanceResult = New-Object AzStackHciAddNodeTarget
        $instanceResult.Name = 'AzStackHci_AddNode_Local_Credential_Check'
        $instanceResult.Title = 'Test Local Credential'
        $instanceResult.Severity = 'Critical'
        $instanceResult.Description = "Checking Local Credential is valid"
        $instanceResult.Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-prerequisites'
        $instanceResult.TargetResourceID = 'Local Credential'
        $instanceResult.TargetResourceName = 'Local Credential'
        $instanceResult.TargetResourceType = 'Credential'
        $instanceResult.Timestamp = [datetime]::UtcNow
        $instanceResult.HealthCheckSource = $ENV:EnvChkrId
        $AdditionalData = @()
        $AdditionalData += New-Object -TypeName PSObject -Property @{
            Source    = $ENV:COMPUTERNAME
            Resource  = 'Local Credential'
            Detail    = $detail
            Status    = $status
            TimeStamp = [datetime]::UtcNow
        }
        $instanceResult.AdditionalData = $AdditionalData
        $instanceResult.Status = if ($AdditionalData.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }
        return $instanceResult
    }
    catch
    {
        throw ("Error testing local credential {0} on {1}. Error: {2}" -f $LocalCredential.UserName, $ComputerName, $_.Exception)
    }
    finally
    {
        Remove-UtilityModule -PsSession $PsSession
        $PsSession | Remove-PSSession -ErrorAction SilentlyContinue
    }
}

function Test-ComputerName {
    [CmdletBinding()]
    param (
        [string]
        $ComputerName
    )
    try
    {
        $clusterNodes = Get-ClusterNode | Select-Object -ExpandProperty Name
        $result = $ComputerName -in $clusterNodes
        $detail = $lanTxt.TestComputerName -f $computerName, $result, $false
        if (-not $result)
        {
            $status = 'Succeeded'
            Log-Info $detail
        }
        else
        {
            $status = 'Failed'
            Log-Info $detail -Type Warning
        }
        $instanceResult = New-Object AzStackHciAddNodeTarget
        $instanceResult.Name = 'AzStackHci_AddNode_ComputerName_Check'
        $instanceResult.Title = 'Test ComputerName'
        $instanceResult.Severity = 'Critical'
        $instanceResult.Description = "Checking ComputerName is not in use"
        $instanceResult.Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-prerequisites'
        $instanceResult.TargetResourceID = $ComputerName
        $instanceResult.TargetResourceName = $ComputerName
        $instanceResult.TargetResourceType = 'Computer Name'
        $instanceResult.Timestamp = [datetime]::UtcNow
        $instanceResult.HealthCheckSource = $ENV:EnvChkrId
        $AdditionalData = @()
        $AdditionalData += New-Object -TypeName PSObject -Property @{
            Source    = $ENV:COMPUTERNAME
            Resource  = $ComputerName
            Detail    = $detail
            Status    = $status
            TimeStamp = [datetime]::UtcNow
        }
        $instanceResult.AdditionalData = $AdditionalData
        $instanceResult.Status = if ($AdditionalData.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }
        $instanceResult
    }
    catch
    {
        throw ($lanTxt.NodeNameCheckError -f $ComputerName, $_.Exception)
    }
}

function Test-ComputerIP {
    [CmdletBinding()]
    param (
        [string]
        $ComputerName,

        [pscredential]
        $LocalCredential,

        [System.Net.IPAddress]
        $Ipv4
    )
    try
    {
        # if no IP is provided, we assume this is repair and return
        if (-not $Ipv4)
        {
            return
        }
        $sb = {
            $args[0] -in (Get-NetIPAddress | Select-Object -Expand IPAddress)
        }
        $result = [bool](Microsoft.PowerShell.Core\Invoke-Command -ComputerName $ComputerName -Credential $LocalCredential -ScriptBlock $sb -ArgumentList $Ipv4 -ErrorAction SilentlyContinue)
        $detail = $lanTxt.TestComputerIp -f $computerName, $Ipv4, $result, $true
        if ($result)
        {
            $status = 'Succeeded'
            Log-Info $detail
        }
        else
        {
            $status = 'Failed'
            Log-Info $detail -Type Warning
        }
        $instanceResult = New-Object AzStackHciAddNodeTarget
        $instanceResult.Name = 'AzStackHci_AddNode_ComputerIp_Check'
        $instanceResult.Title = 'Test Computer IP'
        $instanceResult.Severity = 'Critical'
        $instanceResult.Description = "Checking Computer has IP configured"
        $instanceResult.Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-prerequisites'
        $instanceResult.TargetResourceID = $ComputerName
        $instanceResult.TargetResourceName = $ComputerName
        $instanceResult.TargetResourceType = 'Ip Configuration'
        $instanceResult.Timestamp = [datetime]::UtcNow
        $instanceResult.HealthCheckSource = $ENV:EnvChkrId
        $AdditionalData = @()
        $AdditionalData += New-Object -TypeName PSObject -Property @{
            Source    = $ENV:COMPUTERNAME
            Resource  = $ComputerName
            Detail    = $detail
            Status    = $status
            TimeStamp = [datetime]::UtcNow
        }
        $instanceResult.AdditionalData = $AdditionalData
        $instanceResult.Status = if ($AdditionalData.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }
        $instanceResult
    }
    catch
    {
        throw ("Error testing IP {0} is set locally on {1}. Error: {2}" -f $Ipv4, $ComputerName, $_.Exception)
    }
}

function Test-Quorum {
    <#
    .SYNOPSIS
        Check if single node cluster has correct quorum configuration
    #>

    [CmdletBinding()]
    param()
    try {
        # Get the current number of nodes in the cluster
        $CurrentNumberOfNodes = (Get-ClusterNode).Count

        # if the cluster has only one node, check if the quorum is set to NodeandFileShareMajority or CloudWitness
        if ($CurrentNumberOfNodes -eq 1)
        {
            $Quorum = Get-ClusterQuorum
            $detail = $lanTxt.QuorumCheck -f $Quorum.Cluster.Name, $CurrentNumberOfNodes, $Quorum.QuorumResource.Name,'File Share Witness or Cloud Witness'
            if ($Quorum.QuorumResource.Name -eq 'File Share Witness' -or $Quorum.QuorumResource.Name -eq 'Cloud Witness')
            {
                Log-Info $detail
                $Status = 'Succeeded'
            }
            else
            {
                Log-Info $detail -Type Warning
                $Status = 'Failed'
            }
            $instanceResult = New-Object AzStackHciAddNodeTarget
            $instanceResult.Name = 'AzStackHci_AddNode_Quorum_Resource_Check'
            $instanceResult.Title = 'Test Quorum Resource'
            $instanceResult.Severity = 'Critical'
            $instanceResult.Description = "Checking Quorum Resource is set to File Share Witness or CloudWitness"
            $instanceResult.Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-prerequisites'
            $instanceResult.TargetResourceID = 'Quorum Resource'
            $instanceResult.TargetResourceName = 'Quorum Resource'
            $instanceResult.TargetResourceType = 'Quorum Resource'
            $instanceResult.Timestamp = [datetime]::UtcNow
            $instanceResult.HealthCheckSource = $ENV:EnvChkrId
            $AdditionalData = @()
            $AdditionalData += New-Object -TypeName PSObject -Property @{
                Source    = $Quorum.Cluster.Name
                Resource  = 'Quorum Resource'
                Detail    = $detail
                Status    = $status
                TimeStamp = [datetime]::UtcNow
            }
            $instanceResult.AdditionalData = $AdditionalData
            $instanceResult.Status = $status
            $instanceResult
        }
    }
    catch {
        throw $_
    }
}

Export-ModuleMember -Function Test-*
# SIG # Begin signature block
# MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCARviPInucTi0OI
# Wc/eshkMb19g4GXQMMHvZUinhebMhKCCDXYwggX0MIID3KADAgECAhMzAAACy7d1
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIKcunZ7HnbSCNSOOGvNO8uOE
# o5jBmRTLUhZrrSNUrrkgMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEABuyQmzFWwis7VM31xc/uIXXydzCaJMqFpkokdy/evGjXON/o48ORtswQ
# zfxUR9ijyhTvjx7GF2/qDWGEBMacsX8irmRUExGxfixnPWQnY7x/zd8Dq0O25CUj
# yIvmtOQXamBQkhcQr6F4VH8cIwkpfGxHRRong51WbeRO9Sm0j2YsvM+p4vL2rx6A
# R20xNHJM7My8vEWnNtg6hIwmRBuJ5qf/JaOH+AMJHck/S45j+vCCpWJw6QAtKdp7
# DQtQo6qJF3GZAGoOkH85OFkrxo4eONELH2fVfg2VlCxubqxnDC0A9lQysTh+qXbp
# x+WI2tj8jioI5RRMDd7SGGBnFK8GzaGCFykwghclBgorBgEEAYI3AwMBMYIXFTCC
# FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCDGDTB1oMsLhLgd2ijy6LJKgIF37ODNb0HNNl+QlxBzpgIGZD/SWvKH
# GBMyMDIzMDQyMDIxMTYwNi45MzNaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# Ojg2REYtNEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAG3IScaB6IqhkYAAQAAAbcwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw
# OTIwMjAyMjE0WhcNMjMxMjE0MjAyMjE0WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4NkRGLTRC
# QkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMf9z1dQNBNkTBq3HJclypjQ
# cJIlDAgpvsw4vHJe06n532RKGkcn0V7p65OeA1wOoO+8NsopnjPpVZ8+4s/RhdMC
# MNPQJXoWdkWOp/3puIEs1fzPBgTJrdmzdyUYzrAloICYx722gmdpbNf3P0y5Z2gR
# O48sWIYyYeNJYch+ZfJzXqqvuvq7G8Nm8IMQi8Zayvx+5dSGBM5VYHBxCEjXF9EN
# 6Qw7A60SaXjKjojSpUmpaM4FmVec985PNdSh8hOeP2tL781SBan92DT19tfNHv9H
# 0FAmE2HGRwizHkJ//mAZdS0s6bi/UwPMksAia5bpnIDBOoaYdWkV0lVG5rN0+ltR
# z9zjlaH9uhdGTJ+WiNKOr7mRnlzYQA53ftSSJBqsEpTzCv7c673fdvltx3y48Per
# 6vc6UR5e4kSZsH141IhxhmRR2SmEabuYKOTdO7Q/vlvAfQxuEnJ93NL4LYV1IWw8
# O+xNO6gljrBpCOfOOTQgWJF+M6/IPyuYrcv79Lu7lc67S+U9MEu2dog0MuJIoYCM
# iuVaXS5+FmOJiyfiCZm0VJsJ570y9k/tEQe6aQR9MxDW1p2F3HWebolXj9su7zrr
# ElNlHAEvpFhcgoMniylNTiTZzLwUj7TH83gnugw1FCEVVh5U9lwNMPL1IGuz/3U+
# RT9wZCBJYIrFJPd6k8UtAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUs/I5Pgw0JAVh
# DdYB2yPII8l4tOwwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAA2dZMybhVxSXTbJ
# zFgvNiMCV5/Ayn5UuzJU495YDtcefold0ehR9QBGBhHmAMt10WYCHz2WQUyM3mQD
# 4IsHfEL1JEwgG9tGq71ucn9dknLBHD30JvbQRhIKcvFSnvRCCpVpilM8F/YaWXC9
# VibSef/PU2GWA+1zs64VFxJqHeuy8KqrQyfF20SCnd8zRZl4YYBcjh9G0GjhJHUP
# AYEx0r8jSWjyi2o2WAHD6CppBtkwnZSf7A68DL4OwwBpmFB3+vubjgNwaICS+fkG
# VvRnP2ZgmlfnaAas8Mx7igJqciqq0Q6An+0rHj1kxisNdIiTzFlu5Gw2ehXpLrl5
# 9kvsmONVAJHhndpx3n/0r76TH+3WNS9UT9jbxQkE+t2thif6MK5krFMnkBICCR/D
# VcV1qw9sg6sMEo0wWSXlQYXvcQWA65eVzSkosylhIlIZZLL3GHZD1LQtAjp2A5F7
# C3Iw4Nt7C7aDCfpFxom3ZulRnFJollPHb3unj9hA9xvRiKnWMAMpS4MZAoiV4O29
# zWKZdUzygp7gD4WjKK115KCJ0ovEcf92AnwMAXMnNs1o0LCszg+uDmiQZs5eR7jz
# dKzVfF1z7bfDYNPAJvm5pSQdby3wIOsN/stYjM+EkaPtUzr8OyMwrG+jpFMbsB4c
# fN6tvIeGtrtklMJFtnF68CcZZ5IAMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# 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
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4
# NkRGLTRCQkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAyGdBGMObODlsGBZmSUX2oWgfqcaggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOfrojIwIhgPMjAyMzA0MjAxOTM2MThaGA8yMDIzMDQyMTE5MzYxOFowdDA6Bgor
# BgEEAYRZCgQBMSwwKjAKAgUA5+uiMgIBADAHAgEAAgIMMDAHAgEAAgIRfTAKAgUA
# 5+zzsgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAK62k5yWX5XeZYe6Ut5M
# 5He3dgkAMaBmUY9LbZnKMjpKE66wFNwZTdkhKm2Q8+mOI8/SAY0dGuBtLGpPhL6b
# fPEsguIa8DfW6MM/iTN5xa0L8kM/sKox3rXa2Jj+XLUL1i4hQqpJtnE+CrjkPf8g
# MYhQZTTqU7AUPohXGibCERjlMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTACEzMAAAG3IScaB6IqhkYAAQAAAbcwDQYJYIZIAWUDBAIB
# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx
# IgQgZj6P5RhilM86PQvCH+qwfUCUCZT7WLggmnd6sOFGoUMwgfoGCyqGSIb3DQEJ
# EAIvMYHqMIHnMIHkMIG9BCBsJ3jTsh7aL8hNeiYGL5/8IBn8zUfr7/Q7rkM8ic1w
# QTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABtyEn
# GgeiKoZGAAEAAAG3MCIEIEamCcG+0iTXj5zOL/u6O+MyUE6maZTR7KkbcqCdCoXJ
# MA0GCSqGSIb3DQEBCwUABIICAEq/zO5fAEThr238RDAP9Xy27ECNyMkk1eDj5vW0
# GVV/vIT/eDCbdwINdQJ6q1n9IRlmMMXHkVX7zzDV8VXcL9MAWufmP/SLjfHuK8k7
# 9ZlHZQQsGJF1wnfV8j+Db+V1Avw5rl+6RCbVaUVgCUIW3OmKHjH28KN8YkIn6ckw
# XWhUoR64eMME4gCEVuuJgNdmcAzLWHjsV2XILmUSFOrw0ajIP4ZzOmN/JDraLDSg
# uKAmvPEZR/HDolWXnNHqY5G/P2EG/vbhXGuZVBmrnfF724eHIpBWPhIkCBY91Smz
# HCZHzD2chYslHmndMw1x3qsotSta/5PJX48krfRYL1SEqaKmZVAbbFfgNQvcSx0z
# US6oo+4AMrs1DJolQKyuwVy1BPDPymgyzMriqLtdVtoDkwxW9nNTH8X3AdyLrqAp
# eBDFLinI6/JduUcKG9BHREXM5HN46eABs9NezU6hHW0tZYa5XdXTLKitd72DI0UB
# 7jwfoJFd8T6aatvN0OKDSt3ozxG+QwGaW81Gna1bwn3YqpLuGzcOXC4D8LRLJlEV
# tD1UZPtC3crbrDQfsQQaQZioAza6WuGPh1uhkR0lHOHMeip+KqOSb5NmoK6JqECy
# RW1aXClBkZ9xuxuhLI7sQNBd5FzIlQSZbdZKUKZUd90sYl+xwJFoFuyZxLWf7p3t
# FgGi
# SIG # End signature block