AzStackHciMOCStack/AzStackHci.MOCStack.Helpers.psm1

Import-LocalizedData -BindingVariable VvsTxt -FileName AzStackHci.MOCStack.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 AzStackHciMOCStackVolumeTarget : HealthModel
{
    # Attribute for performing check
    [string]$ExpectedMOCStackVolumeSize
    [string]$CurrentMOCStackVolumeSize
}

class AzStackHciMOCStackCPUCoreTarget : HealthModel
{
    # Attribute for performing check
    [string]$ExpectedMOCStackCPUCoreCount
    [string]$CurrentMOCStackCPUCoreCount
}

class AzStackHciMOCStackRAMTarget : HealthModel
{
    # Attribute for performing check
    [string]$ExpectedMOCStackRAM
    [string]$CurrentMOCStackRAM
}

class AzStackHciMOCStackPortTarget : HealthModel
{
    # Attribute for performing check
    [string]$ExpectedEnablePort
    [string]$DisablePort
}

class AzStackHciMOCStackFirewallURLTarget : HealthModel
{
    # Attribute for performing check
    [string]$ExpectedAllowedURL
    [string]$BlockedFirewallURL
}

class AzStackHciMOCStackNodeAgentTarget : HealthModel {}

class AzStackHciMOCStackClouldAgentTarget : HealthModel {}

class AzStackHciMOCStackClusterNodeTarget : HealthModel {}

function Test-MOCStackVolume
{
    <#
    .SYNOPSIS
        Verify if the available free space in the volume, meets the size threshold required by MOCStack during the deployment scenario.
    .DESCRIPTION
        Verify if the available free space in the volume meets the size threshold required by MOCStack during the deployment scenario.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for MOCStack validation. e.g. Deployment, Update, etc
    .PARAMETER PhysicalDriveLetter
        Specify PhysicalDriveLetter used to validation MOCStack Volume. Default C drive is used as MOCStack 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
    {
        $defalutHC4MOCStackVolumeSize = 50
        $defalutVMMOCStackVolumeSize = 20
        Log-Info -Message ($VvsTxt.MOCStackVolumeStartInfo) -Type Info
        Log-Info -Message ($VvsTxt.MOCStackVolumeDriveInfo -f $PhysicalDriveLetter)
        $lowDiskMsg = ($VvsTxt.LowDiskSpaceMsg -f $PhysicalDriveLetter)

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

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

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

        # Run scriptblock
        $MOCStackVolumeSizeResult = Invoke-Command -Session $PsSession -ScriptBlock $testVolumeSb -ArgumentList $defalutHC4MOCStackVolumeSize, $defalutVMMOCStackVolumeSize, $PhysicalDriveLetter, $lowDiskMsg


        # build result
        $now = [datetime]::UtcNow
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($MOCStackVolumeSizeResult.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }

        $volumeResult = New-Object -Type AzStackHciMOCStackVolumeTarget -Property @{
            Name               = 'AzStackHci_MOCStack_Volume'
            Title              = 'MOCStack Volume Requirement'
            Severity           = 'Critical'
            Description        = 'Test to check MOCStack volume size requirement is met'
            Tags               =  $OperationType
            Remediation        = 'Free up disk space for MOCStack Volume'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $MOCStackVolumeSizeResult.HardwareType | Get-Unique
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $MOCStackVolumeSizeResult
            HealthCheckSource  = $ENV:EnvChkrId
            ExpectedMOCStackVolumeSize = $MOCStackVolumeSizeResult.ExpectedMOCStackVolumeSize
            CurrentMOCStackVolumeSize = $MOCStackVolumeSizeResult.CurrentMOCStackVolumeSize
        }
        return $volumeResult
    }
    catch
    {
        throw $_
    }
}

function Test-MOCStackCPUCore
{
    <#
    .SYNOPSIS
        Verify if the host node meets the minimum CPU count requirement for MOCStack configuration
    .DESCRIPTION
        Verify if the host node meets the minimum CPU count requirement for MOCStack configuration
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for MOCStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (

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

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

    try
    {
        $defalutCPUCount = 4
        Log-Info -Message ($VvsTxt.MOCStackCPUStartInfo) -Type Info
        $lowCpuMsg = ($lvsTxt.LowCPuMsg)


        # Scriptblock to test MOCStackCpu core on each server
        $testCpuSb = {
            $AdditionalData = @()
            $status = "Succeeded"
            $errorMsg = $null
            $hardwareType = $null
            $expectedMOCStackCpuCoreCount = $args[0]
            $resourceMsg = $null
            $cpuCount = 1

            try
            {
                # Check CPU core count on each machince
                $cpuCount = $(Get-CimInstance -ClassName Win32_Processor | Select-Object -Property NumberOfCores).NumberOfCores
                if ($cpuCount -lt $expectedMOCStackCpuCoreCount)
                {
                    $resourceMsg = "MOCStack CPU validation expects at least the host to have $($expectedMOCStackCpuCoreCount) cores."
                    throw $args[1]
                }
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $resourceMsg = "Error occurred in Environment Validator MOCStack Cpu test."
                $status = "Failed"
            }
            finally
            {

                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    HardwareType  = $hardwareType
                    ExpectedMOCStackCPUCoreCount = $expectedMOCStackCpuCoreCount
                    CurrentMOCStackCPUCoreCount = $cpuCount
                    Status    = $status
                    Source    = $ENV:COMPUTERNAME
                    Resource  = $resourceMsg
                    Detail    = $errorMsg
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $MOCStackCPUResult = Invoke-Command -Session $PsSession -ScriptBlock $testCpuSb -ArgumentList $defalutCPUCount, $lowCpuMsg


        # build result
        $now = [datetime]::UtcNow
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($MOCStackCPUResult.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }

        $cpuCoreResult = New-Object -Type AzStackHciMOCStackCPUCoreTarget -Property @{
            Name               = 'AzStackHci_MOCStack_CpuCoreCount'
            Title              = 'MOCStack CPU Requirement'
            Severity           = 'Critical'
            Description        = 'Test to check MOCStack CPU requirement is met'
            Tags               =  $OperationType
            Remediation        = 'Upgrage the node CPU core configuration'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $MOCStackCPUResult.HardwareType | Get-Unique
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $MOCStackCPUResult
            HealthCheckSource  = $ENV:EnvChkrId
            ExpectedMOCStackCPUCoreCount = $MOCStackCPUResult.ExpectedMOCStackCPUCoreCount
            CurrentMOCStackCPUCoreCount = $MOCStackCPUResult.CurrentMOCStackCPUCoreCount
        }
        return $cpuCoreResult
    }
    catch
    {
        throw $_
    }
}

function Test-MOCStackMemory
{
    <#
    .SYNOPSIS
        Verify physical memory ie RAM of the node satisfies the minimum requirements of MOCStack.
    .DESCRIPTION
        Verify physical memory ie RAM of the node satisfies the minimum requirements of MOCStack.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for MOCStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (

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

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

    try
    {
        $defalutRAMSizeInGB = 8
        Log-Info -Message ($VvsTxt.MOCStackMemoryStartInfo) -Type Info
        $lowMemoryMsg = ($VvsTxt.LowMemoryMsg)

        # Scriptblock to test physical memory on each server
        $testRAMSb = {
            $AdditionalData = @()
            $status = "Succeeded"
            $errorMsg = $null
            $hardwareType = $null
            $expectedRAMSizeInGB = $args[0]
            $freeSpaceInGB = 1
            $resourceMsg = $null

            try
            {
                # Check physical memory size
                $totalRAM = (Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum).sum
                $totalRAMInGB = [int]($totalRAM / 1GB)
                if ($totalRAMInGB -lt $defalutRAMSize)
                {
                    $resourceMsg = "MOCStack physical memory size validation expects at least the host to have $($expectedRAMSizeInGB) GB."
                    throw $args[1]
                }
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $resourceMsg = "Error occurred in Environment Validator MOCStack physical memory size test."
                $status = "Failed"
            }
            finally
            {
                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    HardwareType  = $hardwareType
                    ExpectedMOCStackRAM = $expectedRAMSizeInGB
                    CurrentMOCStackRAM = $totalRAMInGB
                    Status    = $status
                    Source    = $ENV:COMPUTERNAME
                    Resource  = $resourceMsg
                    Detail    = $errorMsg
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $MOCStackRAMSizeResult = Invoke-Command -Session $PsSession -ScriptBlock $testRAMSb -ArgumentList $defalutRAMSizeInGB, $lowMemoryMsg
        # build result
        $now = [datetime]::UtcNow
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($MOCStackRAMSizeResult.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }

        $RAMResult = New-Object -Type AzStackHciMOCStackRAMTarget -Property @{
            Name               = 'AzStackHci_MOCStack_RAM_Size'
            Title              = 'MOCStack RAM Requirement'
            Severity           = 'Critical'
            Description        = 'Test to check MOCStack RAM requirement is met'
            Tags               =  $OperationType
            Remediation        = 'Upgrage the node PhysicalMemory configuration'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $MOCStackRAMSizeResult.HardwareType | Get-Unique
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $MOCStackRAMSizeResult
            HealthCheckSource  = $ENV:EnvChkrId
            ExpectedMOCStackRAM = $MOCStackRAMSizeResult.ExpectedMOCStackRAM
            CurrentMOCStackRAM = $MOCStackRAMSizeResult.CurrentMOCStackRAM
        }
        return $RAMResult
    }
    catch
    {
        throw $_
    }
}

function Test-MOCStackNetworkPort
{
    <#
    .SYNOPSIS
        Verify that the required network ports for MOCStack are open.
    .DESCRIPTION
        Verify that the required network ports for MOCStack are open.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for MOCStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (

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

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

    try
    {
        $portList = '443','80'
        Log-Info -Message ($VvsTxt.MOCStackPortInfo) -Type Info
        $disabledPortMsg = ($VvsTxt.DisablePortMsg)

        # Scriptblock to test network port on each server
        $testPortSb = {
            $AdditionalData = @()
            $status = "Succeeded"
            $errorMsg = $null
            $hardwareType = $null
            $expectedPortList = $args[0]
            $failedPort = $null
            $resourceMsg = $null

            try
            {
                # Check each network port is enabled on the node
                foreach ($port in $expectedPortList)
                {
                    $tcpSucceeded = Test-NetConnection -Port $port -InformationLevel Quiet
                    if($tcpSucceeded -ne $true)
                    {
                        $failedPort += " $port,"
                        $status = "Failed"
                    }
                }
                
                # Check overall network port enable status
                if ($status -eq 'Failed')
                {
                    $resourceMsg = "The network port validation for MOCStack requires $($failedPort) to be enabled."
                    throw $args[1]
                }
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $resourceMsg = "Error occurred in Environment Validator MOCStack network port test."
                $status = "Failed"
            }
            finally
            {
                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    HardwareType  = $hardwareType
                    ExpectedEnablePort = $portList
                    DisablePort = $failedPort
                    Status    = $status
                    Source    = $ENV:COMPUTERNAME
                    Resource  = $resourceMsg
                    Detail    = $errorMsg
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $MOCStackPortResult = Invoke-Command -Session $PsSession -ScriptBlock $testPortSb -ArgumentList $portList, $disabledPortMsg
        # build result
        $now = [datetime]::UtcNow
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($MOCStackPortResult.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }

        $PortResult = New-Object -Type AzStackHciMOCStackPortTarget -Property @{
            Name               = 'AzStackHci_MOCStack_Network_Port'
            Title              = 'MOCStack Network Port Requirement'
            Severity           = 'Critical'
            Description        = 'Test to check MOCStack Network Port requirement is met'
            Tags               =  $OperationType
            Remediation        = 'Enable the mandatory network port required for MOCStack'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $MOCStackPortResult.HardwareType | Get-Unique
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $MOCStackPortResult
            HealthCheckSource  = $ENV:EnvChkrId
            ExpectedEnablePort = $MOCStackPortResult.ExpectedEnablePort
            DisablePort = $MOCStackPortResult.DisablePort
        }
        return $PortResult
    }
    catch
    {
        throw $_
    }
}

function Test-MOCStackFirewallUrl
{
    <#
    .SYNOPSIS
        Verify that the necessary URL for MOCStack is added to the allowlist in the firewall.
    .DESCRIPTION
        Verify that the necessary URL for MOCStack is added to the allowlist in the firewall.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for MOCStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (

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

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

    try
    {
        Log-Info -Message ($VvsTxt.MOCStackFirewallURLInfo) -Type Info
        $blockedURLMsg = ($VvsTxt.BlockedURLMsg)
        $fireWallURLList = @{
            'mcr.microsoft.com'= 443
            'azure.com'= 443
            'windows.net'= 443
            'sts.windows.net'= 443
            'hybridaks.azurecr.io'= 443
            'guestnotificationservice.azure.com'= 443
            'ecpacr.azurecr.io'= 443
            'microsoft.com'= 443
            'azurearcfork8s.azurecr.io'= 443
            'pythonhosted.org'= 443
            'msk8s.sf.tlu.dl.delivery.mp.microsoft.com'= 443
            'msk8s.b.tlu.dl.delivery.mp.microsoft.com'= 80
            'msk8s.api.cdp.microsoft.com'= 443
            'msk8s.sb.tlu.dl.delivery.mp.microsoft.com'= 443
            'kvamanagementoperator.azurecr.io'= 443
            'linuxgeneva-microsoft.azurecr.io'= 443 
        }     
        
        # Scriptblock to test firewall URLs in allowlist on each server
        $testFirewllURLSb = {
            $AdditionalData = @()
            $status = "Succeeded"
            $errorMsg = $null
            $hardwareType = $null
            $expectedfireWallURLList = $args[0]
            $blockedURLList = $null
            $resourceMsg = $null

            try
            {
                # Check each URLs is in allowlist on the node
                foreach ($url in $expectedfireWallURLList.keys)
                {
                    $retryCount = 0
                    $connectionSucceeded = $false
                    while (!$connectionSucceeded -and $retryCount -lt 5)
                    {
                        $checkConnection = Test-NetConnection -ComputerName $url -Port $expectedfireWallURLList[$url]
                        $connectionSucceeded = $checkConnection.TcpTestSucceeded
                        $retryCount ++
                    }
                    
                    if($connectionSucceeded -eq $false)
                    {
                        $blockedURLList += " $url,"
                        $status = "Failed"
                    }
                }
                
                # Check overall allowlist URLs status
                if ($status -eq 'Failed')
                {
                    $resourceMsg = "The MOCStack requires $($blockedURLList) URL to be in firewall allowed list."
                    throw $args[1]
                }
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $resourceMsg = "Error occurred in Environment Validator MOCStack Firewall Url test."
                $status = "Failed"
            }
            finally
            {
                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    HardwareType  = $hardwareType
                    ExpectedAllowedURL = $expectedfireWallURLList
                    BlockedFirewallURL = $blockedURLList
                    Status    = $status
                    Source    = $ENV:COMPUTERNAME
                    Resource  = $resourceMsg
                    Detail    = $errorMsg
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $MOCStackURLResult = Invoke-Command -Session $PsSession -ScriptBlock $testFirewllURLSb -ArgumentList $fireWallURLList, $blockedURLMsg
        # build result
        $now = [datetime]::UtcNow
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($MOCStackURLResult.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }

        $UrlResult = New-Object -Type AzStackHciMOCStackFirewallURLTarget -Property @{
            Name               = 'AzStackHci_MOCStack_Firewall_URL'
            Title              = 'MOCStack Firewall URL allowed list Requirement'
            Severity           = 'Critical'
            Description        = 'Test to check MOCStack Firewall URL allowed list requirement is met'
            Tags               =  $OperationType
            Remediation        = 'Enable the mandatory MOCStack URL'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $MOCStackURLResult.HardwareType | Get-Unique
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $MOCStackURLResult
            HealthCheckSource  = $ENV:EnvChkrId
            ExpectedAllowedURL = $MOCStackURLResult.ExpectedAllowedURL
            BlockedFirewallURL = $MOCStackURLResult.BlockedFirewallURL
        }
        return $UrlResult
    }
    catch
    {
        throw $_
    }
}

function Test-MOCStackNodeAgents
{
    <#
    .SYNOPSIS
        Verify MOC NodeAgent Service is up and running.
    .DESCRIPTION
        Verify MOC NodeAgent Service is up and running.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for MOCStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (

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

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

    try
    {
        Log-Info -Message ($VvsTxt.MOCStackNodeAgentInfo) -Type Info
        $nodeAgentFailMsg = ($VvsTxt.NodeAgentFail)
        
        # Scriptblock to check MOC Node agent service on each server
        $testNodeAgentSb = {
            $AdditionalData = @()
            $status = "Succeeded"
            $errorMsg = $null
            $hardwareType = $null
            $expectedAgentState = 'Running'
            $currentAgentStatus = $null
            $resourceMsg = $null

            try
            {
                # Check Node agent service is in running state
                $currentAgentStatus = $(Get-Service -Name 'wssdagent' | Select Status).Status
                if($currentAgentStatus -ne $expectedAgentState)
                {
                    $status = "Failed"
                    $resourceMsg = "On node $($ENV:COMPUTERNAME), MOC NodeAgent service is in $($currentAgentStatus) status"
                    throw $args[0]
                }
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $resourceMsg = "Error occurred in Environment Validator MOCStack Node Agent test."
                $status = "Failed"
            }
            finally
            {
                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    HardwareType  = $hardwareType
                    Status    = $status
                    Source    = $ENV:COMPUTERNAME
                    Resource  = $resourceMsg
                    Detail    = $errorMsg
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $MOCStackNodeAgentResult = Invoke-Command -Session $PsSession -ScriptBlock $testNodeAgentSb -ArgumentList $nodeAgentFailMsg
        # build result
        $now = [datetime]::UtcNow
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($MOCStackNodeAgentResult.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }

        $NodeAgentResult = New-Object -Type AzStackHciMOCStackNodeAgentTarget -Property @{
            Name               = 'AzStackHci_MOCStack_NodeAgent_Service'
            Title              = 'MOCStack Node agent Service State'
            Severity           = 'Critical'
            Description        = 'Test to check if the MOCStack NodeAgent service is in the expected running state.'
            Tags               =  $OperationType
            Remediation        = 'Ensure the MOC NodeAgent service is up and running.'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $MOCStackNodeAgentResult.HardwareType | Get-Unique
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $MOCStackNodeAgentResult
            HealthCheckSource  = $ENV:EnvChkrId
        }
        return $NodeAgentResult
    }
    catch
    {
        throw $_
    }
}

function Test-MOCStackCloudAgent
{
    <#
    .SYNOPSIS
        Verify MOC CloudAgent Service is in an online state.
    .DESCRIPTION
        Verify MOC CloudAgent Service is in an online state.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for MOCStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (

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

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

    try
    {
        Log-Info -Message ($VvsTxt.MOCStackCloudAgentInfo) -Type Info
        $cloudAgentFailMsg = ($VvsTxt.CloudAgentFail)
        
        # Scriptblock to check MOC cloud agent service state
        $testCloudAgentSb = {
            $AdditionalData = @()
            $status = "Succeeded"
            $errorMsg = $null
            $hardwareType = $null
            $expectedCloudAgentState = 'Online'
            $currentCloudAgentStatus = $null
            $resourceMsg = $null

            try
            {
                # Check Cloud agent service is in Online state
                $currentCloudAgentStatus = $(Get-ClusterResource -Name 'MOC Cloud Agent Service' | select State).State
                if($currentCloudAgentStatus -ne $expectedCloudAgentState)
                {
                    $status = "Failed"
                    $resourceMsg = "MOC CloudAgent service is in $($currentCloudAgentStatus) state"
                    throw $args[0]
                }
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $resourceMsg = "Error occurred in Environment Validator MOCStack CloudAgent Agent test."
                $status = "Failed"
            }
            finally
            {
                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    HardwareType  = $hardwareType
                    Status    = $status
                    Source    = $ENV:COMPUTERNAME
                    Resource  = $resourceMsg
                    Detail    = $errorMsg
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $MOCStackCloudAgentResult = Invoke-Command -Session $PsSession -ScriptBlock $testCloudAgentSb -ArgumentList $cloudAgentFailMsg
        # build result
        $now = [datetime]::UtcNow
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($MOCStackCloudAgentResult.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }

        $CloudAgentResult = New-Object -Type AzStackHciMOCStackClouldAgentTarget -Property @{
            Name               = 'AzStackHci_MOCStack_CloudAgent_Service'
            Title              = 'MOCStack CloudAgent Service State'
            Severity           = 'Critical'
            Description        = 'Test to check if the MOCStack CloudAgent service is in the expected Online state.'
            Tags               =  $OperationType
            Remediation        = 'Ensure the MOC CloudAgent service is in Online state.'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $MOCStackCloudAgentResult.HardwareType | Get-Unique
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $MOCStackCloudAgentResult
            HealthCheckSource  = $ENV:EnvChkrId
        }
        return $CloudAgentResult
    }
    catch
    {
        throw $_
    }
}

function Test-MOCStackClusterNode
{
    <#
    .SYNOPSIS
        Verify cluster node is up and running.
    .DESCRIPTION
        Verify cluster node is up and running.
    .PARAMETER PsSession
        Specify the PsSession(s) used to validation from.
    .PARAMETER OperationType
        Specify the Operation Type to target for MOCStack validation. e.g. Deployment, Update, etc
    #>

    [CmdletBinding()]
    param (

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

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

    try
    {
        Log-Info -Message ($VvsTxt.ClusterNodeInfo) -Type Info
        $clusterNodeFailMsg = ($VvsTxt.ClusterNodeFail)
        
        # Scriptblock to check cluster node state
        $testClusterNodeSb = {
            $AdditionalData = @()
            $status = "Succeeded"
            $errorMsg = $null
            $hardwareType = $null
            $expectedClusterNodeState = 'Up'
            $offlineNode =  $null
            $resourceMsg = $null

            try
            {
                # Check cluster node state is up and running
                $offlineNode = $(Get-ClusterNode | Where State -ne $expectedClusterNodeState)
                if($offlineNode -ne $null -and $offlineNode.count -gt 0)
                {
                    $status = "Failed"
                    $resourceMsg = "Cluster node $($offlineNode.Name), in $($offlineNode.State) state"
                    throw $args[0]
                }
            }
            catch
            {
                $errorMsg = $_.Exception.Message
                $resourceMsg = "Error occurred in Environment Validator MOCStack Cluster Node test."
                $status = "Failed"
            }
            finally
            {
                $AdditionalData += New-Object -TypeName PsObject -Property @{
                    HardwareType  = $hardwareType
                    Status    = $status
                    Source    = $ENV:COMPUTERNAME
                    Resource  = $resourceMsg
                    Detail    = $errorMsg
                }
            }
            return $AdditionalData
        }

        # Run scriptblock
        $MOCStackClusterNodeResult = Invoke-Command -Session $PsSession -ScriptBlock $testClusterNodeSb -ArgumentList $cloudAgentFailMsg
        # build result
        $now = [datetime]::UtcNow
        $targetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
        $aggregateStatus = if ($MOCStackClusterNodeResult.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' }

        $ClusterNodeResult = New-Object -Type AzStackHciMOCStackClusterNodeTarget -Property @{
            Name               = 'AzStackHci_MOCStack_ClusterNode_State'
            Title              = 'MOCStack Cluster Node State'
            Severity           = 'Critical'
            Description        = 'Test to check if the cluster node is in the expected up state.'
            Tags               =  $OperationType
            Remediation        = 'Ensure the Cluster Node is in up and running state.'
            TargetResourceID   = $targetComputerName
            TargetResourceName = $targetComputerName
            TargetResourceType = $MOCStackClusterNodeResult.HardwareType | Get-Unique
            Timestamp          = $now
            Status             = $aggregateStatus
            AdditionalData     = $MOCStackClusterNodeResult
            HealthCheckSource  = $ENV:EnvChkrId
        }
        return $ClusterNodeResult
    }
    catch
    {
        throw $_
    }
}

Export-ModuleMember -Function Test-MOCStackCPUCore
Export-ModuleMember -Function Test-MOCStackMemory
Export-ModuleMember -Function Test-MOCStackNetworkPort
Export-ModuleMember -Function Test-MOCStackFirewallUrl
Export-ModuleMember -Function Test-MOCStackClusterNode
Export-ModuleMember -Function Test-MOCStackCloudAgent
Export-ModuleMember -Function Test-MOCStackNodeAgents
# Excluding Volume check function
#Export-ModuleMember -Function Test-MOCStackVolume
# SIG # Begin signature block
# MIInwgYJKoZIhvcNAQcCoIInszCCJ68CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDlmeuBuL+8coXc
# YzlQkj1Zt6KlyDBuJPBBLKkoh+6GpqCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGaIwghmeAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIPikz+TzboEWw8b9sjhwO6iQ
# oI/CjC3j5sQ8i5AOd0e/MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAOfJzMcV9CAeUv82gWkZIvDcdpU5OfzNHtvgk+30ZOFRm/HAnK15ZDvSg
# hFaktZkjbr+83PYnt1MQ9VfScdArwkWlJl9LxMrHgmdiX8iQ8fMnNNaAu6/4vNzf
# ITcKu0fRsAvIH0PzhHHjsiNZGIz03Y96UKM0DsWQmdy7vJale1YzGfYXoeLBPkyV
# LNbjespPscta0Opr75dRnZ/MNjruwyGtSns+2m4senX/2DIV2/KyGUx3II12k/NQ
# mrw4tj3lnXwaXn2zN6c+bWF/cJyoMHInToG5ChPu1qL2xiBlzt2pnRf+eBI57gFw
# XRsBukc+VA8+jwDOJxPgel+iUYJagqGCFywwghcoBgorBgEEAYI3AwMBMYIXGDCC
# FxQGCSqGSIb3DQEHAqCCFwUwghcBAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCBKUJD2ZEdSj6urPC7DvOWkDCjrbPfIbl4O1wWF70ic6AIGZD/Uan5f
# GBMyMDIzMDUxMDE2NTg1OS4xODhaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjNCRDQtNEI4MC02OUMzMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIRezCCBycwggUPoAMCAQICEzMAAAG0+4AIRAXSLfoAAQAAAbQwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw
# OTIwMjAyMjA5WhcNMjMxMjE0MjAyMjA5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozQkQ0LTRC
# ODAtNjlDMzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALRHpp5lBzJCH7zortuyvOmW
# 8FoZLBsFe9g5dbhnaq9qSpvpn86E/mJ4JKvWixH/lw7QA8gPtiiGVNIjvFhu/XiY
# 889vX5WaQSmyoPMZdj9zvXa5XrkMN05zXzTePkCIIzF6RN7cTxezOyESymTIjrdx
# X5BVlZolyQAOxNziMCYKYYNPbYd0786fDE/PhzrRt23a0Xf8trvFa0LEEy2YlcE2
# eqg2CjU/D0GZe8Ra0kjt0M12vdS4qWZ2Dpd7IhiQwnntQWu19Ytd3UBR8SpeRX+C
# cw3bjgWfOXtla6chctWt2shlMwayMOfY4TG4yMPWFXELfZFFp7cgpjZNeVsmwkvo
# V6RAwy1Y9V+VvbJ5qFtartN/rp6a0I1kGlbjuwX3L0HTVXcikqgHistXk9h3HOZ9
# WgFXlxZurG1SZmcz0BEEdya+1vGHE45KguYU9qq2LiHGBjn9z4+DqnV5tUKobsLb
# JMb4r+8st2fj8SacSsftnusxkWqEJiJS34P2uNlzVR03+ls6+ZO0NcO79LgP7BbI
# MipiOx8yh19PMQw0piaKFwOW7Q+gdJcfy6rOkG+CrYZwOzdiBHSebIzCIch2cAa+
# 38w7JFP/koKdlJ36qzdVXWv4G/qZpWycIvDKYbxJWM40+z2Stg5uHqK3I8e09kFX
# txCHpS7hm8c8m25WaEU5AgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUy0SF5fGUuDqc
# uxIot07eOMwy2X4wHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBABLRDwWMKbeCYqEq
# tI6Bs8KmF+kqDR+2G6qYAK3ZZ63bert7pCkRJbihFaktl2o18cdFJFxnOF4vXadm
# 0sabskJ05KviEMJIO6dXSq8AGtr3Zmjc895q0mnlBLuNMgk4R8KrkJMHqBuHqkUW
# XtfTrVUpgwzQt2UOiINKs+/b4r14MuXRVpOJ6cQOS8UhkeMAWl2iLlYaBGtOr3f/
# f9mLEPfWwoke0sSUbdV60OZCRh1ItBYYM9efKr14H5qu6jan6n00prEEa7W3uGb/
# 1/qj6P5emnvkqy5HI0X69DjVdLxVbjSsegm/dA+S4DaXPcfFf6iBxK/iV21l1upg
# EVVajUApl5VR40wY4XF8EpmnUdTqLXDf7CqdhDjPST2K/OjvWPyQGQvc7oPapYyk
# 66GU32AOyyHXJj6+vbtRUg/+ory+h0R2Xf5NhC+xbWcMzXEUXRRf1YKZDsRyH6r4
# 12pm8KDKE/r7Rk7aoKK7oYUpNGzNRf6QaYv5z2bVTSxkzWivFrepLHGwvRun9PYM
# /8AQSTgZr0yzzjk/97WghkqCaAwAVpyvg3uaYnuCl/AccSkGyb8c+70bFSeUephs
# fgb2r+QI7Mb2WcOnkJpCNLz0XJMS/UwlQn1ktLsiCpsqOk3aLJ2wTv6LK3u69I0v
# QB/LKRKlZYRXKUDXzoPwr3UtsTVTMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# 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
# tB1VM1izoXBm8qGCAtcwggJAAgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoz
# QkQ0LTRCODAtNjlDMzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAZZzYkPObl/ZzeCkSbf4B5CceCQiggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOgGAIAwIhgPMjAyMzA1MTAxOTM3MzZaGA8yMDIzMDUxMTE5MzczNlowdzA9Bgor
# BgEEAYRZCgQBMS8wLTAKAgUA6AYAgAIBADAKAgEAAgIPMgIB/zAHAgEAAgIVPDAK
# AgUA6AdSAAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAFZq4SXruRd45Yt+
# ofSN7Ol0nzequTa3Jd0g1YYMb7bqLlMeXeC52Ex6OXYPCE5u2JzpsUQ+Ux6BqaZZ
# z2MPUaybQP75hnOWobgSBb9VHJ+RCVGzdiZ1PdOEMEdmOMItiz6yZe+tNMECZ3AQ
# axhNIVqA5bgu30+WyXBzrAy0MQqLMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAG0+4AIRAXSLfoAAQAAAbQwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQgWUjBrARUQCNLRVw8nilFoqMLMkxjFWGUfnkPWSb0/IEwgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCDTyPd75qMwcAZRcb36/6xJa3hT0eLse71ysdp4
# twH3BjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# tPuACEQF0i36AAEAAAG0MCIEIHSA/VKCqpY+ZWe8BgcfhCO8stlh60b/CiexrRlN
# d/isMA0GCSqGSIb3DQEBCwUABIICADpc452IE0zxr09AfhlcGvIIAUJhoVf/p3uU
# a9fk8kJbnX+tf9buR9h29yBA9aXxXoHe7YRXJ0KQup3cHoEMMHfgoXUeVYgUlyzL
# c9sv9+WQ0iYA2B1FNe9GNIcuqDIJOHA20M0GTEc/5HqDkinTy9FBLLE+SL72WcAs
# QjeAivW6c9OBrKVWE0E2YWqlfdhh+UzrN3nsCPsPMFUoIijOw/qNky+RdOw6ZtYD
# Y75AUDyok1z5q4g4quVGPCblowwga5TSExr28Qvbxuw6MSfPzyZge3uw6XhAfprg
# qMOAYb7eTEBw6h4te2HlSnaJeFLmXzIbjC2cp09E2e2RofD4h1kHQHaL2LO9nRCo
# cTAd1sAugtG0RHD0+oo5+XBogWOMWekMfe58WchZsG7OlF2zFXmr17scX7RNnel0
# GShn26QwdLcIJCZ/65+c+eafhD+ume9slCNkz57bfgwKqFEU6dovMKI83/Wx2j46
# ktOSEikfpqEdB19WU2FvHQaCcNaAbsug46PZpmxeGmG+ijqwaOMYmDjJsK+MX3hi
# ZXgoqrxuUZEs+OYberahQHiw1tpNZA82INrOcLsH4iGSQb/7GxyJORgCiP9Aq7jO
# 6xsdQLtZ3PJzqgA7av+8jfs17Qit5M/EREUz9/abI0zna7cf24pY6Vy6ZVKJTZ7k
# upPl7d0x
# SIG # End signature block