AzStackHciMOCStack/AzStackHci.MOCStack.Helpers.psm1

Import-LocalizedData -BindingVariable VvsTxt -FileName AzStackHci.MOCStack.Strings.psd1
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 = "SUCCESS"
            $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 = "FAILURE"
            }
            finally
            {
                $AdditionalData = @{
                    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
        foreach ($result in $MOCStackVolumeSizeResult)
        {
            $params = @{
                Name               = 'AzStackHci_MOCStack_Volume'
                Title              = 'MOCStack Volume Requirement'
                DisplayName        = 'MOCStack Volume Requirement {0}' -f $result.Source
                Severity           = 'CRITICAL'
                Description        = 'Test to check MOCStack volume ({0}) size requirement ({1}) is met' -f $PhysicalDriveLetter, $MOCStackVolumeSizeResult.ExpectedMOCStackVolumeSize
                Tags               = @{
                    OperationType = $OperationType
                }
                Remediation        = 'Free up disk space for MOCStack Volume'
                TargetResourceID   = "$($result.Source)/$PhysicalDriveLetter"
                TargetResourceName = $PhysicalDriveLetter
                TargetResourceType = 'Computer'
                Timestamp          = [datetime]::UtcNow
                Status             = $result.Status
                AdditionalData     = $result
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
    }
    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 = "SUCCESS"
            $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 -Property NumberOfCores).NumberOfCores | Measure-Object -Sum).Sum
                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 = "FAILURE"
            }
            finally
            {

                $AdditionalData = @{
                    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
        foreach ($result in $MOCStackCPUResult)
        {
            $params = @{
                Name               = 'AzStackHci_MOCStack_CpuCoreCount'
                Title              = 'MOCStack CPU Requirement'
                DisplayName        = 'MOCStack CPU Requirement {0}' -f $result.Source
                Severity           = 'CRITICAL'
                Description        = 'Test to check MOCStack CPU core count ({0}) requirement is met' -f $defalutCPUCount
                Tags               = @{
                    OperationType = $OperationType
                }
                Remediation        = 'Upgrage the node CPU core configuration'
                TargetResourceID   = $result.Source
                TargetResourceName = $result.Source
                TargetResourceType = 'Computer'
                Timestamp          = [datetime]::UtcNow
                Status             = $result.Status
                AdditionalData     = $result
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
    }
    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 = "SUCCESS"
            $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 = "FAILURE"
            }
            finally
            {
                $AdditionalData += @{
                    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
        foreach ($result in $MOCStackRAMSizeResult)
        {
            $params = @{
                Name               = 'AzStackHci_MOCStack_RAM_Size'
                Title              = 'MOCStack RAM Requirement'
                DisplayName        = 'MOCStack RAM Requirement {0}' -f $result.Source
                Severity           = 'CRITICAL'
                Description        = 'Test to check MOCStack RAM ({0}) requirement is met' -f $defalutRAMSizeInGB
                Tags               = @{
                    OperationType = $OperationType
                }
                Remediation        = 'Upgrage the node PhysicalMemory configuration'
                TargetResourceID   = $result.Source
                TargetResourceName = $result.Source
                TargetResourceType = 'Computer'
                Timestamp          = [datetime]::UtcNow
                Status             = $result.Status
                AdditionalData     = $result
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
    }
    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
    {
        # Don't do this test in a proxy environment
        if (Get-IsProxyEnabled)
        {
            return
        }

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

        # Scriptblock to test network port on each server
        $testPortSb = {
            $AdditionalData = @()
            $status = "SUCCESS"
            $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)
                {
                    # Added retry logic
                    $retryCount = 0
                    $tcpSucceeded = $false
                    while (!$tcpSucceeded -and $retryCount -lt 5)
                    {
                        $tcpSucceeded = Test-NetConnection -Port $port -InformationLevel Quiet
                        $retryCount ++
                    }

                    # Validate the TCP connection
                    if($tcpSucceeded -ne $true)
                    {
                        $failedPort += " $port,"
                        $status = "FAILURE"
                    }
                }

                # Check overall network port enable status
                if ($status -eq 'FAILURE')
                {
                    $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 = "FAILURE"
            }
            finally
            {
                $AdditionalData += @{
                    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
        foreach ($result in $MOCStackPortResult)
        {
            # build result
            $params = @{
                Name               = 'AzStackHci_MOCStack_Network_Port'
                Title              = 'MOCStack Network Port Requirement'
                DisplayName        = 'MOCStack Network Port Requirement {0}' -f $result.Source
                Severity           = 'CRITICAL'
                Description        = 'Test to check MOCStack Network Port requirement is met'
                Tags               = @{
                    OperationType = $OperationType
                }
                Remediation        = 'Enable the mandatory network port required for MOCStack'
                TargetResourceID   = $result.Source
                TargetResourceName = $result.Source
                TargetResourceType = 'Computer'
                Timestamp          = [datetime]::UtcNow
                Status             = $result.Status
                AdditionalData     = $result
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
    }
    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
    )

    # This test has been converted to a service-defined target towards an external endpoint using connectivity validator
    try
    {
        Log-Info -Message ($VvsTxt.MOCStackFirewallURLInfo) -Type Info
        $MOCStackURLResult = Invoke-AzStackHciConnectivityValidation -Exclude (Get-AzStackHciConnectivityServiceName | ? {$_ -ne 'MOC Stack'}) -PassThru | Where-Object Name -like *MOCStack*
        # build result
        foreach ($result in $MOCStackURLResult)
        {
            $params = @{
                Name               = 'AzStackHci_MOCStack_Firewall_URL'
                Title              = 'MOCStack Firewall URL allowed list Requirement'
                DisplayName        = 'MOCStack Firewall URL allowed list Requirement {0}' -f $result.AdditionalData.Source
                Severity           = 'WARNING'
                Description        = 'Test to check MOCStack Firewall URL allowed list requirement is met'
                Tags               = @{
                    OperationType = $OperationType
                }
                Remediation        = 'https://learn.microsoft.com/en-us/azure-stack/hci/manage/azure-arc-vm-management-prerequisites#firewall-url-requirements'
                TargetResourceID   = $result.TargetResourceID
                TargetResourceName = $result.TargetResourceName
                TargetResourceType = $result.TargetResourceType
                Timestamp          = [datetime]::UtcNow
                Status             = $result.Status
                AdditionalData     = $result.AdditionalData
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
    }
    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 = "SUCCESS"
            $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 = "FAILURE"
                    $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 = "FAILURE"
            }
            finally
            {
                $AdditionalData += @{
                    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
        foreach ($result in $MOCStackNodeAgentResult)
        {
            $params = @{
                Name               = 'AzStackHci_MOCStack_NodeAgent_Service'
                Title              = 'MOCStack Node agent Service State'
                DisplayName        = 'MOCStack Node agent Service State {0}' -f $result.Source
                Severity           = 'CRITICAL'
                Description        = 'Test to check if the MOCStack NodeAgent service is in the expected running state'
                Tags               = @{
                    OperationType = $OperationType
                }
                Remediation        = 'Ensure the MOC NodeAgent service is in Online state'
                TargetResourceID   = $result.Source
                TargetResourceName = $result.Source
                TargetResourceType = 'Computer'
                Timestamp          = [datetime]::UtcNow
                Status             = $result.Status
                AdditionalData     = $result
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
    }
    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 = "SUCCESS"
            $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 = "FAILURE"
                    $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 = "FAILURE"
            }
            finally
            {
                $AdditionalData += @{
                    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
        foreach ($result in $MOCStackCloudAgentResult)
        {
            $params = @{
                Name               = 'AzStackHci_MOCStack_CloudAgent_Service'
                Title              = 'MOCStack CloudAgent Service State'
                DisplayName        = 'MOCStack CloudAgent Service State {0}' -f $result.Source
                Severity           = 'CRITICAL'
                Description        = 'Test to check if the MOCStack CloudAgent service is in the expected running state'
                Tags               = @{
                    OperationType = $OperationType
                }
                Remediation        = 'Ensure the MOC CloudAgent service is in Online state'
                TargetResourceID   = $result.Source
                TargetResourceName = $result.Source
                TargetResourceType = 'Computer'
                Timestamp          = [datetime]::UtcNow
                Status             = $result.Status
                AdditionalData     = $result
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
    }
    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 = "SUCCESS"
            $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 = "FAILURE"
                    $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 = "FAILURE"
            }
            finally
            {
                $AdditionalData += @{
                    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
        foreach ($result in $MOCStackClusterNodeResult)
        {
            $params = @{
                Name               = 'AzStackHci_MOCStack_ClusterNode_State'
                Title              = 'MOCStack Cluster Node State'
                DisplayName        = 'MOCStack Cluster Node State {0}' -f $result.Source
                Severity           = 'CRITICAL'
                Description        = 'Test to check if the cluster node is in the expected up state'
                Tags               = @{
                    OperationType = $OperationType
                }
                Remediation        = 'Ensure the MOC CloudAgent service is in Online state'
                TargetResourceID   = $result.Source
                TargetResourceName = $result.Source
                TargetResourceType = 'Computer'
                Timestamp          = [datetime]::UtcNow
                Status             = $result.Status
                AdditionalData     = $result
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
    }
    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
# MIInzgYJKoZIhvcNAQcCoIInvzCCJ7sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDqB6i/7brzZldn
# 0CQ+AHn2fVgShjCYslub1iE6zGmtLaCCDYUwggYDMIID66ADAgECAhMzAAADri01
# UchTj1UdAAAAAAOuMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwODU5WhcNMjQxMTE0MTkwODU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQD0IPymNjfDEKg+YyE6SjDvJwKW1+pieqTjAY0CnOHZ1Nj5irGjNZPMlQ4HfxXG
# yAVCZcEWE4x2sZgam872R1s0+TAelOtbqFmoW4suJHAYoTHhkznNVKpscm5fZ899
# QnReZv5WtWwbD8HAFXbPPStW2JKCqPcZ54Y6wbuWV9bKtKPImqbkMcTejTgEAj82
# 6GQc6/Th66Koka8cUIvz59e/IP04DGrh9wkq2jIFvQ8EDegw1B4KyJTIs76+hmpV
# M5SwBZjRs3liOQrierkNVo11WuujB3kBf2CbPoP9MlOyyezqkMIbTRj4OHeKlamd
# WaSFhwHLJRIQpfc8sLwOSIBBAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhx/vdKmXhwc4WiWXbsf0I53h8T8w
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMTgzNjAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AGrJYDUS7s8o0yNprGXRXuAnRcHKxSjFmW4wclcUTYsQZkhnbMwthWM6cAYb/h2W
# 5GNKtlmj/y/CThe3y/o0EH2h+jwfU/9eJ0fK1ZO/2WD0xi777qU+a7l8KjMPdwjY
# 0tk9bYEGEZfYPRHy1AGPQVuZlG4i5ymJDsMrcIcqV8pxzsw/yk/O4y/nlOjHz4oV
# APU0br5t9tgD8E08GSDi3I6H57Ftod9w26h0MlQiOr10Xqhr5iPLS7SlQwj8HW37
# ybqsmjQpKhmWul6xiXSNGGm36GarHy4Q1egYlxhlUnk3ZKSr3QtWIo1GGL03hT57
# xzjL25fKiZQX/q+II8nuG5M0Qmjvl6Egltr4hZ3e3FQRzRHfLoNPq3ELpxbWdH8t
# Nuj0j/x9Crnfwbki8n57mJKI5JVWRWTSLmbTcDDLkTZlJLg9V1BIJwXGY3i2kR9i
# 5HsADL8YlW0gMWVSlKB1eiSlK6LmFi0rVH16dde+j5T/EaQtFz6qngN7d1lvO7uk
# 6rtX+MLKG4LDRsQgBTi6sIYiKntMjoYFHMPvI/OMUip5ljtLitVbkFGfagSqmbxK
# 7rJMhC8wiTzHanBg1Rrbff1niBbnFbbV4UDmYumjs1FIpFCazk6AADXxoKCo5TsO
# zSHqr9gHgGYQC2hMyX9MGLIpowYCURx3L7kUiGbOiMwaMIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAOuLTVRyFOPVR0AAAAA
# A64wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIECu
# gvZEj3HE0SOnWJJoCXjeoGVu2GwKqIWl/8e1X0xqMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAmjVDv1tjkCCqoag25ygS4VlSD17srXhOgi0N
# 3Tb4RZkQ1aMhBhqaXxMeBLBKXTeqOen1QhtvZVIVGfzPOLiG4XcyMH5n2L67FlFW
# cuhUuQJvR1kKi49bEJ4WvqvXevNmNHU5CrkNlfFxZHLB6UQ4guXM3MWQMiyuJt+K
# SKk31uPKRTJLdSG84AA6ntqmguSe6kOe92N2iJuznPe97LzfHyX4kXMjY/Cci5jt
# krj2mjfzhuDDa4ygoWvf0rCmfQtVhF0zA8pYTKPNXQ/zcVjLl6gaKt3B5cSJPwXu
# Ks5iKWuQ/SUGMeXlal2NElU/QCtJ6cdqzHbDpkFVp7wa8PuMvKGCFykwghclBgor
# BgEEAYI3AwMBMYIXFTCCFxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCBrz9KnyGzroC2jDOCSVQjt6MXnFefZuEbM
# 0zup4GuLywIGZdYWoaI9GBMyMDI0MDMxMTE4MTcxOC4zMDFaMASAAgH0oIHYpIHV
# MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT
# HVRoYWxlcyBUU1MgRVNOOjg2REYtNEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAHdXVcd
# ldStqhsAAQAAAd0wDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTAwHhcNMjMxMDEyMTkwNzA5WhcNMjUwMTEwMTkwNzA5WjCB0jELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z
# b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjo4NkRGLTRCQkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKhO
# A5RE6i53nHURH4lnfKLp+9JvipuTtctairCxMUSrPSy5CWK2DtriQP+T52HXbN2g
# 7AktQ1pQZbTDGFzK6d03vYYNrCPuJK+PRsP2FPVDjBXy5mrLRFzIHHLaiAaobE5v
# FJuoxZ0ZWdKMCs8acjhHUmfaY+79/CR7uN+B4+xjJqwvdpU/mp0mAq3earyH+AKm
# v6lkrQN8zgrcbCgHwsqvvqT6lEFqYpi7uKn7MAYbSeLe0pMdatV5EW6NVnXMYOTR
# KuGPfyfBKdShualLo88kG7qa2mbA5l77+X06JAesMkoyYr4/9CgDFjHUpcHSODuj
# lFBKMi168zRdLerdpW0bBX9EDux2zBMMaEK8NyxawCEuAq7++7ktFAbl3hUKtuzY
# C1FUZuUl2Bq6U17S4CKsqR3itLT9qNcb2pAJ4jrIDdll5Tgoqef5gpv+YcvBM834
# bXFNwytd3ujDD24P9Dd8xfVJvumjsBQQkK5T/qy3HrQJ8ud1nHSvtFVi5Sa/ubGu
# YEpS8gF6GDWN5/KbveFkdsoTVIPo8pkWhjPs0Q7nA5+uBxQB4zljEjKz5WW7BA4w
# pmFm24fhBmRjV4Nbp+n78cgAjvDSfTlA6DYBcv2kx1JH2dIhaRnSeOXePT6hMF0I
# l598LMu0rw35ViUWcAQkUNUTxRnqGFxz5w+ZusMDAgMBAAGjggFJMIIBRTAdBgNV
# HQ4EFgQUbqL1toyPUdpFyyHSDKWj0I4lw/EwHwYDVR0jBBgwFoAUn6cVXQBeYl2D
# 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv
# ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy
# MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l
# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUB
# Af8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD
# ggIBAC5U2bINLgXIHWbMcqVuf9jkUT/K8zyLBvu5h8JrqYR2z/eaO2yo1Ooc9Shy
# vxbe9GZDu7kkUzxSyJ1IZksZZw6FDq6yZNT3PEjAEnREpRBL8S+mbXg+O4VLS0LS
# mb8XIZiLsaqZ0fDEcv3HeA+/y/qKnCQWkXghpaEMwGMQzRkhGwcGdXr1zGpQ7HTx
# vfu57xFxZX1MkKnWFENJ6urd+4teUgXj0ngIOx//l3XMK3Ht8T2+zvGJNAF+5/5q
# Bk7nr079zICbFXvxtidNN5eoXdW+9rAIkS+UGD19AZdBrtt6dZ+OdAquBiDkYQ5k
# VfUMKS31yHQOGgmFxuCOzTpWHalrqpdIllsy8KNsj5U9sONiWAd9PNlyEHHbQZDm
# i9/BNlOYyTt0YehLbDovmZUNazk79Od/A917mqCdTqrExwBGUPbMP+/vdYUqaJsp
# upBnUtjOf/76DAhVy8e/e6zR98PkplmliO2brL3Q3rD6+ZCVdrGM9Rm6hUDBBkvY
# h+YjmGdcQ5HB6WT9Rec8+qDHmbhLhX4Zdaard5/OXeLbgx2f7L4QQQj3KgqjqDOW
# InVhNE1gYtTWLHe4882d/k7Lui0K1g8EZrKD7maOrsJLKPKlegceJ9FCqY1sDUKU
# hRa0EHUW+ZkKLlohKrS7FwjdrINWkPBgbQznCjdE2m47QjTbMIIHcTCCBVmgAwIB
# AgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0
# IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1
# WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O
# 1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZn
# hUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t
# 1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxq
# D89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmP
# frVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSW
# rAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv
# 231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zb
# r17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYcten
# IPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQc
# xWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17a
# j54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQAB
# MCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQU
# n6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEw
# QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E
# b2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/
# MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ
# oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p
# Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB
# BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v
# Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3h
# LB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x
# 5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74p
# y27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1A
# oL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbC
# HcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB
# 9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNt
# yo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3
# rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcV
# v7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A24
# 5oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lw
# Y1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB
# 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk
# TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjo4NkRGLTRCQkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUANiNHGWXbNaDPxnyi
# DbEOciSjFhCggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN
# BgkqhkiG9w0BAQUFAAIFAOmZoQEwIhgPMjAyNDAzMTEyMzI1NTNaGA8yMDI0MDMx
# MjIzMjU1M1owdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA6ZmhAQIBADAHAgEAAgIN
# BzAHAgEAAgIRWDAKAgUA6ZrygQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEE
# AYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GB
# AD8sy6RabmttR5SLAKUzHTI4hY+jq6y58sODGUYNeZcUqjCcv7ulB2eWYiFRLHAR
# pQtl2023/IswfGR9Jy6LkGc5JzSKv5sKTDxU8UtRLCbPWVSw5DX+4alUh2ElIRT2
# Z/tHLeHHmAmAMOOiP1nZeOLnoPhGQJ710qAbqOk6fds7MYIEDTCCBAkCAQEwgZMw
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHdXVcdldStqhsAAQAA
# Ad0wDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB
# BDAvBgkqhkiG9w0BCQQxIgQgZoXVLCRLpuwXn3nIWWDHIi4IO7TeH9mTwl0Gw4nd
# p6cwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBh/w4tmmWsT3iZnHtH0Vk3
# 7UCN02lRxY+RiON6wDFjZjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwAhMzAAAB3V1XHZXUraobAAEAAAHdMCIEIBryIMl/D2NplWfzR3uv7wRB
# dd7q9G5PrD4j6RrFktIuMA0GCSqGSIb3DQEBCwUABIICAGrz2IPlRIMEk44dFH3R
# q133jV/DbvaxgRClHESTcM6wbVaJfqWDb2Dt/+QOgpLofmdO+AaUUOacM110Ak8I
# 0gW+ZOnWcnlLiGcttFbgMUVASjWNo6sse6lTKCVHEaFNG02gHVmuhORApI2rHA3B
# qSpfHR8WMrCp/S+SUqk+dC5BZlgXGEcjGtfr4GwPE7NXPJr8H07GMQ38aj8bQ0Xb
# Futdz94Ib+TWGE9Bua+ceNbkhpirfyEDOHTxZHIDNiKLGnvKDByfNf8/GojsKgdW
# YJXFMI5EyRDfHTDWHkhucgKzpyLYOOMMIHowSZe9tvIwsB96iqLMy+0pMSbUWxRg
# atYHq25SAo20wRgqPl8fCrvW7A1eS5RcQ8r3BU/a6hphcgMcPmqN8PLw/9eTfAcc
# iUSY07uRPiL319P235CDC5CTCEzOWm5w4BHcjB20bJUUrVXPcEcsZFfTqawsd2sR
# EjsZ3XsJKf1oSX28+ap+iEmofzwHBko+B3R09bGT/6j2cA0cxSuuhKJG2xF7cOxX
# p2BL6qBQQuaXehmEqtQJoIzaIYjGN2mjbwHcseKyq+l9FLtm9jO+hQ16NuEeuLX6
# +2qftSc+q51uafdkItkvL5ZmBgnts4KKp9jUxUIOHDkIFSBp/D8FfnIq3eOgFeo7
# os4vi6sD/9ueGkpGVNPpA745
# SIG # End signature block