AzStackHciHardware/AzStackHci.Hardware.Helpers.psm1

Import-LocalizedData -BindingVariable lhwTxt -FileName AzStackHci.Hardware.Strings.psd1

function Test-Processor
{
    <#
    .SYNOPSIS
        Test CPU
    .DESCRIPTION
        Test CPU
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $cimParams = @{
                ClassName = 'Win32_Processor'
                Property  = '*'
            }
            $cimData = @(Get-CimInstance @cimParams)
            return (New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                cimData = $cimData
            })
        }
        $remoteOutput = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }
        $cimTest = Test-CimData -Data $remoteOutput -ClassName Processor -Severity WARNING
        $cimData = $remoteOutput.cimData

        $PropertyResult = @()
        $PropertySyncResult = @()
        $matchProperty = @(
            'Caption'
            'Family'
            'Manufacturer'
            'MaxClockSpeed'
            'NumberOfCores'
            'NumberOfEnabledCore'
            'NumberOfLogicalProcessors'
            'ThreadCount'
        )

        $warningDesiredPropertyValue = @{
            AddressWidth                            = @{ value = 64; hint = '64-bit' }
            Architecture                            = @{ value = 9; hint = '64-bit' } # x64
            Availability                            = @{ value = 3; hint = 'Running/Full Power' } # Running/Full Power
            CpuStatus                               = @{ value = 1; hint = 'CPU Enabled' } # CPU Enabled
            DataWidth                               = @{ value = 64; hint = '64-bit' } # x64
            ProcessorType                           = @{ value = 3; hint = 'Central Processor' } # Central Processor
            Status                                  = @{ value = 'OK'; hint = 'OK' }
        }

        $criticalDesiredPropertyValue = @{
            SecondLevelAddressTranslationExtensions = @{ value = $true; hint = 'Virtualization Support' }
            VirtualizationFirmwareEnabled           = @{ value = $true; hint = 'Virtualization Support' }
            VMMonitorModeExtensions                 = @{ value = $true; hint = 'Virtualization Support' }
        }

        # if Hypervisorpresent is all true, SecondLevelAddressTranslationExtensions, VirtualizationFirmwareEnabled, VMMonitorModeExtensions should not be tested
        $CheckHyperVisor = IsHypervisorPresent -PsSession $PsSession
        $hypervisorDtl = ($lhwTxt.HypervisorPresent -f (($CheckHyperVisor  | ForEach-Object {"{0}:{1}" -f $_.Name, $_.HypervisorPresent }) -join ','))
        if (($CheckHyperVisor | Select-Object -ExpandProperty HypervisorPresent) -notcontains $false)
        {
            Log-Info $hypervisorDtl
            Remove-Variable -Name criticalDesiredPropertyValue
        }
        else
        {
            Log-Info $hypervisorDtl -Type CRITICAL
        }
        Log-CimData -cimData $cimData -Properties $matchProperty,$warningDesiredPropertyValue,$criticalDesiredPropertyValue
        $instanceIdStr = 'Write-Output "Machine: $($instance.SystemName), Class: $ClassName, Instance: $($instance.DeviceId)"'
        # Check property sync for nodes individually
        $SystemNames = $cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique
        foreach ($systemName in $SystemNames)
        {
            $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
            Log-Info -Message ($lhwTxt.ProcessorCount -f $systemName, $sData.Count)
            $PropertyResult += Test-DesiredProperty -CimData $sData -desiredPropertyValue $warningDesiredPropertyValue -InstanceIdStr $InstanceIdStr -ValidatorName Hardware -Severity Warning
            if ($criticalDesiredPropertyValue)
            {
                $PropertyResult += Test-DesiredProperty -CimData $sData -desiredPropertyValue $criticalDesiredPropertyValue -InstanceIdStr $InstanceIdStr -ValidatorName Hardware -Severity CRITICAL
            }
            $PropertySyncResult += Test-PropertySync -CimData $sData -MatchProperty $matchProperty -ValidatorName Hardware -Severity Warning
        }
        # Check property sync for all nodes as well
        $PropertySyncResult += Test-PropertySync -CimData $cimData -MatchProperty $matchProperty -ValidatorName Hardware -Severity Warning
        return @($PropertyResult + $PropertySyncResult + $cimTest)
    }
    catch
    {
        throw $_
    }
}

function IsHypervisorPresent
{
    <#
    .SYNOPSIS
        Retrieves HypervisorPresent property from Win32_ComputerSystem
    #>

    [cmdletbinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $cimParams = @{
                ClassName = 'Win32_ComputerSystem'
                Property  = 'HypervisorPresent'
            }
            $cimData = @(Get-CimInstance @cimParams)
            return $cimData
        }
        $cimData = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }
        Log-CimData -cimData $cimData -Properties HypervisorPresent
        return $cimData
    }
    catch
    {
        throw $_
    }
}

function Test-NetAdapter
{
    <#
    .SYNOPSIS
        Test Network Adapter
    .DESCRIPTION
        Test Network Adapter
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $cimData = @(Get-NetAdapter -Physical | Where-Object { $_.NdisMedium -eq 0 -and $_.Status -eq 'Up' -and $_.NdisPhysicalMedium -eq 14 -and $_.PnPDeviceID -notlike 'USB\*'})
            return (New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                cimData = $cimData
            })
        }
        $remoteOutput = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }

        $cimTest = Test-CimData -Data $remoteOutput -ClassName NetAdapter -Severity CRITICAL
        $cimData = $remoteOutput.cimData

        $PropertyResult = @()
        $PropertySyncResult = @()
        $GroupResult = @()
        $CountResult = @()

        # Blocking properties
        $criticalMatchProperty = @(
            'DriverDate'
            'DriverDescription'
            'DriverMajorNdisVersion'
            'DriverMinorNdisVersion'
            'DriverProvider'
            'DriverVersionString'
            'MajorDriverVersion'
            'MinorDriverVersion'
        )

        # non-block warning properties
        $warningMatchProperty = @(
            'ActiveMaximumTransmissionUnit'
            'ReceiveLinkSpeed'
            'Speed'
            'TransmitLinkSpeed'
            'VlanID'
            'MtuSize'
        )

        $desiredPropertyValue = @{
            AdminLocked                                      = $false
            ConnectorPresent                                 = $true
            EndpointInterface                                = $false
            ErrorDescription                                 = $null
            FullDuplex                                       = $true
            HardwareInterface                                = $true
            Hidden                                           = $false
            IMFilter                                         = $false
            InterfaceAdminStatus                             = @{ value = 1; hint = 'Up' } # Up
            InterfaceOperationalStatus                       = @{ value = 1; hint = 'Up' } # Up
            iSCSIInterface                                   = $false
            LastErrorCode                                    = $null
            MediaConnectState                                = @{ value = 1; hint = 'Connected' } # Connected
            MediaDuplexState                                 = 2
            NdisMedium                                       = @{ value = 0; hint = '802.3' } # 802.3
            NdisPhysicalMedium                               = @{ value = 14; hint = '802.3' } # 802.3
            OperationalStatusDownDefaultPortNotAuthenticated = $false
            OperationalStatusDownInterfacePaused             = $false
            OperationalStatusDownLowPowerState               = $false
            OperationalStatusDownMediaDisconnected           = $false
            #PromiscuousMode = $false
            State                                            = @{ value = 2; hint = 'Started' } # 802.3 # Started
            #Status = 'Up'
            Virtual                                          = $false
        }

        $groupProperty = @(
            'DriverDescription'
        )

        Log-CimData -cimData $cimData -Properties $desiredPropertyValue,$warningMatchProperty,$criticalMatchProperty

        $minimum = 1
        $instanceIdStr = 'Write-Output "Machine: $($instance.SystemName), ClassName: $ClassName, Instance: $($instance.Name), Description: $($instance.InterfaceDescription), Address: $($instance.PermanentAddress)"'
        # Check property sync for nodes individually
        $SystemNames = $cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique
        foreach ($systemName in $SystemNames)
        {
            $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
            Log-Info -Message ($lhwTxt.NicCount -f $systemName, $sData.Count)
            # Make sure each system has the requisite number of Network Adapters
            $PropertyResult += Test-DesiredProperty -CimData $sData -desiredPropertyValue $desiredPropertyValue -InstanceIdStr $InstanceIdStr -ValidatorName Hardware -Severity Critical
            $CountResult += Test-Count -CimData $sData -minimum $minimum -ValidatorName 'Hardware' -Severity Critical
        }

        # Check property sync for all nodes as well
        $GroupResult += Test-GroupProperty -CimData $cimData -GroupProperty $groupProperty -MatchProperty $warningMatchProperty -ValidatorName Hardware -Severity Warning
        $GroupResult += Test-GroupProperty -CimData $cimData -GroupProperty $groupProperty -MatchProperty $criticalMatchProperty -ValidatorName Hardware -Severity Critical
        $InstanceCount += Test-InstanceCount -CimData $cimData -Severity Critical -ValidatorName 'Hardware'
        $InstanceCountByGroup += Test-InstanceCountByGroup -CimData $cimData -ValidatorName 'Hardware' -GroupProperty $groupProperty -Severity Critical
        # Finally, the all properties from the $matchProperty array have to be compared for all instances across all nodes.
        return @($PropertyResult + $GroupResult + $CountResult + $InstanceCountByGroup + $InstanceCount + $cimTest)
    }
    catch
    {
        throw $_
    }
}

function Test-MemoryCapacity
{
    <#
    .SYNOPSIS
        Test Memory
    .DESCRIPTION
        Test Memory
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        if ((Get-WmiObject -Class Win32_ComputerSystem).Model -eq "Virtual Machine")
        {
            $environmentType = "Virtual"
            $minimumMemory = 24GB
        } else {
            $environmentType = "Physical"
            $minimumMemory = 32GB
        }
        Log-Info -Message ($lhwTxt.MemoryCapacityRequirement -f $minimumMemory, $environmentType)
        $instanceResults = @()

        $sb = {
            $cimParams = @{
                ClassName = 'Win32_PhysicalMemory'
                Property  = '*'
            }
            $cimData = @(Get-CimInstance @cimParams)
            return (New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                cimData = $cimData
            })
        }
        $remoteOutput = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }

        $cimTest = Test-CimData -Data $remoteOutput -ClassName PhysicalMemory -Severity WARNING
        $cimData = $remoteOutput.cimData
        Log-CimData -cimData $cimData -Properties Capacity

        # Check property sync for nodes individually
        $SystemNames = $cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique
        $totalMemoryLocalNode = $cimData | Where-Object { $_.CimSystemProperties.ServerName -like "$($ENV:COMPUTERNAME)*"} | Measure-Object -Property Capacity -Sum | Select-Object -ExpandProperty Sum
        $instanceResults += foreach ($systemName in $SystemNames)
        {
            $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
            $instanceId = "Machine: $($Instance.CimSystemProperties.ServerName), Class: $ClassName, Instance: All"
            $totalMemory = $sData | Measure-Object -Property Capacity -Sum
            $dtl = $lhwTxt.MemoryCapacity -f $systemName, $totalMemory.Sum, $minimumMemory, $totalMemoryLocalNode
            if ($totalMemory.Sum -lt $minimumMemory -or $totalMemory.Sum -lt $totalMemoryLocalNode)
            {
                $Status = 'FAILURE'
                Log-Info $dtl -Type Warning
            }
            else
            {
                $Status = 'SUCCESS'
                Log-Info $dtl
            }

            $params = @{
                Name               = 'AzStackHci_Hardware_Test_MemoryCapacity'
                Title              = 'Test Memory Capacity'
                DisplayName        = "Test Memory Capacity $systemName"
                Severity           = 'WARNING'
                Description        = 'Checking Memory Capacity'
                Tags               = @{}
                Remediation        = 'https://aka.ms/hci-envch'
                TargetResourceID   = $instanceId
                TargetResourceName = $instanceId
                TargetResourceType = 'Memory'
                Timestamp          = [datetime]::UtcNow
                Status             =  $status
                AdditionalData     = @{
                    Source    = 'Memory Capacity'
                    Resource  = $totalMemory.Sum
                    Detail    = $dtl
                    Status    = $status
                    TimeStamp = [datetime]::UtcNow
                }
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
        return ($instanceResults + $cimTest)
    }
    catch
    {
        throw $_
    }
}

function Test-MemoryProperties
{
    <#
    .SYNOPSIS
        Test Memory
    .DESCRIPTION
        Test Memory
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $cimParams = @{
                ClassName = 'Win32_PhysicalMemory'
                Property  = '*'
            }
            $cimData = @(Get-CimInstance @cimParams)
            return (New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                cimData = $cimData
            })
        }
        $remoteOutput = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }

        $cimTest = Test-CimData -Data $remoteOutput -ClassName PhysicalMemory -Severity WARNING
        $cimData = $remoteOutput.cimData

        # Add EEC property
        $eccSb = {
            if ($this.TotalWidth -gt $this.DataWidth)
            {
                return $true
            }
            else
            {
                return $false
            }
        }
        $cimData | Add-Member -MemberType ScriptProperty -Name ECC -Value $eccSb -Force

        $PropertyResult = @()
        $PropertySyncResult = @()
        $matchProperty = @(
            'ConfiguredClockSpeed'
            'ConfiguredVoltage'
            'MaxVoltage'
            'MemoryType'
            'SMBIOSMemoryType'
            'Speed'
            'TotalWidth'
            'TypeDetail'
        )

        $warningdesiredPropertyValue = @{
            DataWidth  = @{ value = 64; hint = '64-bit' } # x64
            FormFactor = @{ value = 8; hint = 'DIMM' } # DIMM
        }

        $criticaldesiredPropertyValue = @{
            ECC  = $true # Error Correction Code (ECC) memory
        }
        Log-CimData -cimData $cimData -Properties $warningdesiredPropertyValue,$criticaldesiredPropertyValue,$matchProperty
        # Check property sync for nodes individually
        $SystemNames = $cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique
        foreach ($systemName in $SystemNames)
        {
            $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
            $instanceIdStr = 'Write-Output "Machine: $($Instance.CimSystemProperties.ServerName), Class: $ClassName, Instance: $($instance.DeviceLocator), Tag: $($instance.Tag)"'
            $PropertyResult += Test-DesiredProperty -CimData $sData -desiredPropertyValue $warningdesiredPropertyValue -InstanceIdStr $InstanceIdStr -ValidatorName Hardware -Severity Warning
            $PropertyResult += Test-DesiredProperty -CimData $sData -desiredPropertyValue $criticaldesiredPropertyValue -InstanceIdStr $InstanceIdStr -ValidatorName Hardware -Severity CRITICAL
            $PropertySyncResult += Test-PropertySync -CimData $sData -MatchProperty $matchProperty -ValidatorName Hardware -Severity Warning
        }
        # Check property sync for all nodes as well
        $PropertySyncResult += Test-PropertySync -CimData $cimData -MatchProperty $matchProperty -ValidatorName Hardware -Severity Warning
        return @($PropertyResult + $PropertySyncResult + $cimTest)
    }
    catch
    {
        throw $_
    }
}

function Test-Gpu
{
    <#
    .SYNOPSIS
        Test Gpu
    .DESCRIPTION
        Test Gpu
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $cimParams = @{
                ClassName = 'Win32_VideoController'
                Property  = '*'
            }
            $cimData = @(Get-CimInstance @cimParams)
            return (New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                cimData = $cimData
            })
        }
        $remoteOutput = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }

        $cimTest = Test-CimData -Data $remoteOutput -ClassName VideoController -Severity WARNING
        $cimData = $remoteOutput.cimData

        $PropertyResult = @()
        $GroupResult = @()
        $InstanceCount = @()
        $InstanceCountByGroup = @()

        $matchProperty = @(
            'AdapterRam'
            'Name'
            'DriverDate'
            'DriverVersion'
            'VideoMemoryType'
            'VideoProcessor'
        )

        $desiredPropertyValue = @{
            ConfigManagerErrorCode = @{ value = 0; hint = 'The device is working properly' } # The device is working properly
            Status                 = 'OK'
        }

        $groupProperty = @(
            'Name'
        )

        Log-CimData -cimData $cimData -Properties $desiredPropertyValue,$matchProperty
        # Check property sync for nodes individually
        $SystemNames = $cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique
        foreach ($systemName in $SystemNames)
        {
            $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
            $totalGpuRam = $sData | Measure-Object -Property AdapterRam -Sum
            Log-Info -Message ($lhwTxt.TotalGPUMem -f $systemName, $sData.Count, ($totalGpuRam.Sum / 1GB))
            $instanceIdStr = 'Write-Output "Machine: $($instance.SystemName), Class: $ClassName, Instance: $($instance.DeviceID), Name: $($instance.Name)"'
            $PropertyResult += Test-DesiredProperty -CimData $sData -desiredPropertyValue $desiredPropertyValue -InstanceIdStr $InstanceIdStr -ValidatorName Hardware -Severity Warning
        }
        # Check property sync for all nodes as well
        $GroupResult += Test-GroupProperty -CimData $cimData -GroupProperty $groupProperty -MatchProperty $MatchProperty -ValidatorName Hardware -Severity Warning
        $InstanceCount += Test-InstanceCount -CimData $cimData -Severity Warning -ValidatorName 'Hardware'
        $InstanceCountByGroup += Test-InstanceCountByGroup -CimData $cimData -ValidatorName 'Hardware' -GroupProperty $groupProperty -Severity Warning
        return @($GroupResult + $InstanceCount + $InstanceCountByGroup + $PropertyResult + $cimTest)
    }
    catch
    {
        throw $_
    }
}

function Test-Baseboard
{
    <#
    .SYNOPSIS
        Test Baseboard
    .DESCRIPTION
        Test Baseboard
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $cimParams = @{
                ClassName = 'Win32_Bios'
                Property  = '*'
            }
            $cimData = @(Get-CimInstance @cimParams)
             return (New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                cimData = $cimData
            })
        }
        $remoteOutput = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }

        $cimTest = Test-CimData -Data $remoteOutput -ClassName Bios -Severity WARNING
        $cimData = $remoteOutput.cimData

        $PropertyResult = @()
        $PropertySyncResult = @()
        $matchProperty = @(
            #'BiosVersion' # this property is a string array and non-trivial to compare
            'Caption'
            'Description'
            'EmbeddedControllerMajorVersion'
            'EmbeddedControllerMinorVersion'
            'Manufacturer'
            'Name'
            'ReleaseDate'
            'SMBIOSBIOSVersion'
            'SMBIOSMajorVersion'
            'SMBIOSMinorVersion'
            'SoftwareElementId'
            'SystemBiosMajorVersion'
            'SystemBiosMinorVersion'
            'Version'
        )

        $desiredPropertyValue = @{
            SMBIOSPresent        = $true
            SoftwareElementState = @{ value = 3; hint = 'Running' } # Running
            Status               = 'OK'
        }

        Log-CimData -cimData $cimData -Properties $desiredPropertyValue,$matchProperty

        # Check property sync for nodes individually
        $SystemNames = $cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique
        foreach ($systemName in $SystemNames)
        {
            $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
            Log-Info -Message ($lhwTxt.TestBaseboard -f $systemName, $sData.Name, $sData.SerialNumber)
            $instanceIdStr = 'Write-Output "Machine: $($instance.CimSystemProperties.ServerName), Class: $ClassName, Serial: $($instance.SerialNumber)"'
            $PropertyResult += Test-DesiredProperty -CimData $sData -desiredPropertyValue $desiredPropertyValue -InstanceIdStr $InstanceIdStr -ValidatorName Hardware -Severity Warning
            $PropertySyncResult += Test-PropertySync -CimData $sData -MatchProperty $matchProperty -ValidatorName Hardware -Severity Warning
        }
        # Check property sync for all nodes as well
        $PropertySyncResult += Test-PropertySync -CimData $cimData -MatchProperty $matchProperty -ValidatorName Hardware  -Severity Warning
        return @($PropertyResult + $PropertySyncResult + $cimTest)
    }
    catch
    {
        throw $_
    }
}

function Test-Model
{
    <#
    .SYNOPSIS
        Test Hardware Model is the same
    .DESCRIPTION
        Test Hardware Model is the same
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $cimParams = @{
                ClassName = 'Win32_ComputerSystem'
                Property  = '*'
            }
            $cimData = @(Get-CimInstance @cimParams)
            return (New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                cimData = $cimData
            })
        }
        $remoteOutput = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }

        $cimTest = Test-CimData -Data $remoteOutput -ClassName ComputerSystem -Severity CRITICAL
        $cimData = $remoteOutput.cimData

        $PropertySyncResult = @()
        $matchProperty = @(
            'Manufacturer'
            'Model'
        )
        Log-CimData -cimData $cimData -Properties $matchProperty

        # Check property sync for nodes individually
        $SystemNames = $cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique
        foreach ($systemName in $SystemNames)
        {
            $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
            Log-Info -Message ($lhwTxt.TestModel -f $systemName, $sData.Manufacturer, $sData.Model)
            $PropertySyncResult += Test-PropertySync -CimData $sData -MatchProperty $matchProperty -ValidatorName Hardware -Severity Critical
        }
        # Check property sync for all nodes as well
        $PropertySyncResult += Test-PropertySync -CimData $cimData -MatchProperty $matchProperty -ValidatorName Hardware  -Severity Critical
        return @($PropertyResult + $PropertySyncResult + $cimTest)
    }
    catch
    {
        throw $_
    }
}

function Test-PhysicalDisk
{
    <#
    .SYNOPSIS
        Test Physical Disk
    .DESCRIPTION
        Test Physical Disk
    #>

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

        [string]
        $HardwareClass = 'Medium'
    )
    try
    {
        $sb = {
            $fabricOp = $args[0]
            $eceNodeName = $args[1]
            $allowedBusTypes = @('SATA', 'SAS', 'NVMe', 'SCM')
            $allowedMediaTypes = @('HDD', 'SSD', 'SCM')
            $bootPhysicalDisk = Get-Disk | Where-Object {$_.IsBoot -or $_.IsSystem} | Get-PhysicalDisk
            if ($fabricOp -match 'AddNode|Repair')
            {
                if ($eceNodeName -eq $env:COMPUTERNAME)
                {
                    # In AddNode we need a seperate check command to return disks that are spaces disks
                    # to build a reference list for the new node
                    $cimData = @(Get-StorageNode -Name $env:COMPUTERNAME* | `
                        Get-PhysicalDisk -PhysicallyConnected | `
                        Where-Object { `
                            $_.CanPool -eq $false -and `
                            $_.CannotPoolReason -eq 'In a Pool'
                        }
                    )
                }
                else
                {
                    if($fabricOp -like "*AddNode*")
                    {
                        # For AddNode node we expect CanPool true and to match ECE node above
                        $cimData = @(Get-StorageNode -Name $env:COMPUTERNAME* | `
                        Get-PhysicalDisk -PhysicallyConnected | `
                            Where-Object { `
                                $_.BusType -in $allowedBusTypes -and `
                                $_.MediaType -in $allowedMediaTypes -and `
                                $_.DeviceId -notin $bootPhysicalDisk.DeviceId -and `
                                $_.CanPool -eq $true
                            }
                        )
                    }
                    elseif ($fabricOp -like "*Repair*")
                    {
                        # For Repair we ignore CanPool and to match ECE node above
                        $cimData = @(Get-StorageNode -Name $env:COMPUTERNAME* | `
                        Get-PhysicalDisk -PhysicallyConnected | `
                            Where-Object { `
                                $_.BusType -in $allowedBusTypes -and `
                                $_.MediaType -in $allowedMediaTypes -and `
                                $_.DeviceId -notin $bootPhysicalDisk.DeviceId
                            }
                        )
                    }
                    else
                    {
                        throw "Invalid Fabric Operation: $fabricOp"
                    }
                }
            }
            else
            {
                if ($fabricOp -like '*KeepStorage*')
                {
                    $cimData = @(Get-StorageNode -Name $env:COMPUTERNAME* | `
                        Get-PhysicalDisk -PhysicallyConnected | `
                        Where-Object { `
                            $_.BusType -in $allowedBusTypes -and `
                            $_.MediaType -in $allowedMediaTypes -and `
                            $_.DeviceId -notin $bootPhysicalDisk.DeviceId
                        }
                    )
                }
                else
                {
                    $cimData = @(Get-StorageNode -Name $env:COMPUTERNAME* | `
                                Get-PhysicalDisk -PhysicallyConnected | `
                                Where-Object { `
                                    $_.BusType -in $allowedBusTypes -and `
                                    $_.MediaType -in $allowedMediaTypes -and `
                                    $_.DeviceId -notin $bootPhysicalDisk.DeviceId -and `
                                    $_.CanPool -eq $true
                                }
                            )
                }
            }
            return (New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                cimData = $cimData
            })
        }
        $remoteOutput = if ($PsSession)
        {
            # When we are using PsSessions (every ECE fabric operation)
            # Inject our FabricOperation and local computer into the remote session,
            # so canPool expectation can be set for deployment and ScaleOut.
            Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList $ENV:EnvChkrId, $ENV:ComputerName
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }

        $cimTest = Test-CimData -Data $remoteOutput -ClassName PhysicalDisk -Severity CRITICAL
        $cimData = $remoteOutput.cimData

        $PropertyResult = @()
        $GroupResult = @()
        $CountResult = @()
        $InstanceCount = @()
        $InstanceCountByGroup = @()

        $matchProperty = @(
            'FirmwareVersion'
        )

        $groupProperty = @(
            'FriendlyName'
        )

        $warningDesiredPropertyValue = @{
            HealthStatus        = @{ value = @('Healthy', 0); hint = 'Healthy' } # Healthy
            IsIndicationEnabled = @{ value = @($false, $null); hint = 'Indicator Off' }
            OperationalStatus   = @{ value = @('OK', 2); hint = 'OK' }
        }

        Log-CimData -cimData $cimData -Properties $groupProperty,$warningDesiredPropertyValue,$matchProperty, CanPool, CannotPoolReason, Size, PhysicalLocation, UniqueId, SerialNumber

        $instanceIdStr = 'Write-Output "Machine: $($instance.CimSystemProperties.ServerName), Class: $ClassName, Location: $($instance.PhysicalLocation), Unique ID: $($instance.UniqueId), Size: $("{0:N2}" -f ($instance.Size / 1TB)) TB"'
        # Check disk count for nodes individually
        [array]$SystemNames = $cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique
        foreach ($systemName in $SystemNames)
        {
            $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
            $totalSize = $sData | Measure-Object -Property Size -Sum
            Log-Info -Message ($lhwTxt.DiskTotal -f $systemName, $($sData.Count), ('{0:N2}' -f ($totalSize.Sum / 1TB)))
            $PropertyResult += Test-DesiredProperty -CimData $sData -desiredPropertyValue $warningDesiredPropertyValue -InstanceIdStr $InstanceIdStr -ValidatorName Hardware -Severity Warning

            # Split disks into type
            $SSD = $sData | Where-Object {$_.MediaType -match 'SSD|4' -and $_.BusType -match 'SAS|10|SATA|11'}
            $NVMe = $sData | Where-Object {$_.MediaType -match 'SSD|4' -and $_.BusType -match 'NVMe|17'}
            $SCM = $sData | Where-Object {$_.MediaType -match 'SCM|5'}
            $HDD = $sData | Where-Object {$_.MediaType -match 'HDD|3'}
            Log-Info ("Drive types detected HDD: {0}, SSD:{1}, NVMe:{2}, SCM:{3}" -f [bool]$HDD, [bool]$SSD, [bool]$NVMe, [bool]$SCM)

            $systemCountResult = @()
            $countCommonParams = @{
                ValidatorName = 'Hardware'
                Severity = 'CRITICAL'
            }

            # split into medium vs small
            # medium is subject storage spaces direct requirements
            # small is subject to the minimum number of disks being 1.
            # consistency across bustype, mediatype, and size and other property is still checked in the property sync for both form factors.
            if ($HardwareClass -eq 'Medium')
            {
                # As per https://docs.microsoft.com/en-us/windows-server/storage/storage-spaces/storage-spaces-direct-hardware-requirements

                # all flash minimum should be 2
                # Drive type present (capacity only) Minimum drives required (Windows Server) Minimum drives required (Azure Stack HCI)
                # All persistent memory (same model) 4 persistent memory 2 persistent memory
                # All NVMe (same model) 4 NVMe 2 NVMe
                # All SSD (same model) 4 SSD 2 SSD
                if ($SSD -xor $NVMe -xor $SCM)
                {
                    $systemCountResult += Test-Count -cimData $sData -Minimum 2 @countCommonParams
                }

                # Drive type present Minimum drives required
                # Persistent memory + NVMe or SSD 2 persistent memory + 4 NVMe or SSD
                # NVMe + SSD 2 NVMe + 4 SSD
                # NVMe + HDD 2 NVMe + 4 HDD
                # SSD + HDD 2 SSD + 4 HDD

                if ($SCM -and ($NVMe -or $SSD)) {
                    $systemCountResult += Test-Count -cimData $SCM -Minimum 2 @countCommonParams
                    if ($NVMe) {
                        Log-Info ($lhwTxt.MinCountDiskType -f 'NVMe', '4', $systemName )
                        $systemCountResult += Test-Count -cimData $NVMe -Minimum 4 @countCommonParams
                    }
                    else {
                        Log-Info ($lhwTxt.MinCountDiskType -f 'SSD', '4', $systemName )
                        $systemCountResult += Test-Count -cimData $SSD -Minimum 4 @countCommonParams
                    }
                }

                if ($NVMe -and $SSD) {
                    Log-Info ($lhwTxt.MinCountDiskType -f 'NVMe', '2', $systemName )
                    $systemCountResult += Test-Count -cimData $NVMe -Minimum 2 @countCommonParams
                    Log-Info ($lhwTxt.MinCountDiskType -f 'SSD', '4', $systemName )
                    $systemCountResult += Test-Count -cimData $SSD -Minimum 4 @countCommonParams
                }

                if ($NVMe -and $HHD) {
                    Log-Info ($lhwTxt.MinCountDiskType -f 'NVMe', '2', $systemName )
                    $systemCountResult += Test-Count -cimData $NVMe -Minimum 2 @countCommonParams
                    Log-Info ($lhwTxt.MinCountDiskType -f 'HDD', '4', $systemName )
                    $systemCountResult += Test-Count -cimData $HDD -Minimum 4 @countCommonParams
                }

                if ($SSD -and $HHD) {
                    Log-Info ($lhwTxt.MinCountDiskType -f 'SSD', '2', $systemName )
                    $systemCountResult += Test-Count -cimData $SSD -Minimum 2 @countCommonParams
                    Log-Info ($lhwTxt.MinCountDiskType -f 'HDD', '4', $systemName )
                    $systemCountResult += Test-Count -cimData $HDD -Minimum 4 @countCommonParams
                }

                if ($systemCountResult.count -eq 0) {
                    Log-Info "We did not determine the disk combination correctly for $systemName. Checking minimum as per deployment guide." -Type Warning
                    $systemCountResult += Test-Count -cimData $sData -Minimum 3 @countCommonParams
                }
            }
            elseif ($HardwareClass -eq 'Small')
            {
                # here we effectively forego the storage spaces direct requirements and just check for the minimum number of 2 disks
                $systemCountResult += Test-Count -cimData $sData -Minimum 1 @countCommonParams
                # and all nodes should be all flash
                [array]$CheckDisksAreAllFlash = CheckDisksAreAllFlash -CimData $sData
            }
            else
            {
                throw "Invalid HardwareClass: $HardwareClass"
            }
            $CountResult += $systemCountResult
        }
        # Check property sync for all nodes
        $GroupResult += Test-GroupProperty -CimData $cimData -GroupProperty $groupProperty -MatchProperty $matchProperty -ValidatorName Hardware -Severity Warning

        # Split disks into type and check each server has the same count
        $allSSD = $cimData | Where-Object {$_.MediaType -match 'SSD|4' -and $_.BusType -match 'SAS|10|SATA|11'}
        $allNVMe = $cimData | Where-Object {$_.MediaType -match 'SSD|4' -and $_.BusType -match 'NVMe|17'}
        $allSCM = $cimData | Where-Object {$_.MediaType -match 'SCM|5'}
        $allHDD = $cimData | Where-Object {$_.MediaType -match 'HDD|3'}
        $instParams = @{
            ValidatorName = 'Hardware'
            Severity = 'CRITICAL'
        }
        if ($allSSD) {
            Log-Info ($lhwTxt.DiskInstanceCountByType -f 'SSD')
            $InstanceCount += Test-InstanceCount -CimData $allSSD @instParams -NamePostfix "SSD"
        }
        if ($allNVMe) {
            Log-Info ($lhwTxt.DiskInstanceCountByType -f 'NVMe')
            $InstanceCount += Test-InstanceCount -CimData $allNVMe @instParams -NamePostfix "NVMe"
        }
        if ($allSCM) {
            Log-Info ($lhwTxt.DiskInstanceCountByType -f 'SCM')
            $InstanceCount += Test-InstanceCount -CimData $allSCM @instParams -NamePostfix "SCM"
        }
        if ($allHDD) {
            Log-Info ($lhwTxt.DiskInstanceCountByType -f 'HDD')
            $InstanceCount += Test-InstanceCount -CimData $allHDD @instParams -NamePostfix "HDD"
        }

        if (($null -eq $PsSession -or $PsSession.Count -eq 1) -and $null -eq $CheckDisksAreAllFlash)
        {
            # Single Node deployments should be all flash
            [array]$CheckDisksAreAllFlash = CheckDisksAreAllFlash -CimData $cimData
        }

        # Do all servers have the same count regardless of type
        $InstanceCount += Test-InstanceCount -CimData $cimData -Severity Critical -ValidatorName 'Hardware'
        Log-Info ($lhwTxt.DiskInstanceCountByType -f 'ALL')

        # Finally, the all properties from the $matchProperty array (Firmware) have to be compared for all instances
        # across all nodes grouped by property (FriendlyName)
        $InstanceCountByGroup += Test-InstanceCountByGroup -CimData $cimData -ValidatorName 'Hardware' -GroupProperty $groupProperty -Severity Warning
        return @($PropertyResult + $GroupResult + $CountResult + $InstanceCount + $InstanceCountByGroup + $CheckDisksAreAllFlash + $cimTest)
    }
    catch
    {
        throw $_
    }
}

function Test-TpmVersion
{
    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession,

        [Parameter()]
        $version = '2.0'
    )

    $tpms = @()
    $InstanceResults = @()
    $sb = {
            $tpm = Get-CimInstance -Namespace root/cimv2/Security/MicrosoftTpm -ClassName Win32_Tpm -ErrorAction SilentlyContinue
            $result = New-Object -TypeName PSObject -Property @{
                ComputerName = $ENV:COMPUTERNAME
                TpmData = $tpm
            }
        return $result
    }
    $tpms += if ($PsSession)
    {
        Invoke-Command -Session $PsSession -ScriptBlock $sb
    }
    else
    {
        Invoke-Command -ScriptBlock $sb
    }

    Log-CimData -CimData $tpms.TpmData

    foreach ($tpm in $tpms)
    {
        $computerName = $tpm.ComputerName
        # Test properties
        $InstanceResults += foreach ($instance in $tpm.TpmData)
        {
            $instanceId = "Machine: $computerName, Class: Tpm, Manufacturer ID: $($instance.ManufacturerId)"
            $instanceVersion = $instance.SpecVersion -split ',' | Select-Object -First 1
            $status = if ($instanceVersion -eq $version) { 'SUCCESS' } else { 'FAILURE' }
            $params = @{
                Name               = 'AzStackHci_Hardware_Test_Tpm_Version'
                Title              = 'Test TPM Version'
                DisplayName        = "Test TPM Version $computerName"
                Severity           = 'CRITICAL'
                Description        = "Checking TPM for desired version ($version)"
                Tags               = @{}
                Remediation        = 'https://aka.ms/hci-envch'
                TargetResourceID   = $instanceId
                TargetResourceName = $instanceId
                TargetResourceType = 'Tpm'
                Timestamp          = [datetime]::UtcNow
                Status             =  $status
                AdditionalData     = @{
                    Source    = 'Version'
                    Resource  = $instanceVersion
                    Detail    = "$instanceId Tpm version is $instanceVersion. Expected $version"
                    Status    = $status
                    TimeStamp = [datetime]::UtcNow
                }
                HealthCheckSource  = $ENV:EnvChkrId
            }
            $instanceResult = New-AzStackHciResultObject @params

            if ($InstanceResult.AdditionalData.Status -eq 'SUCCESS')
            {
                Log-Info -Message $InstanceResult.AdditionalData.Detail
            }
            else
            {
                Log-Info -Message $InstanceResult.AdditionalData.Detail -Type Warning
            }
            $instanceResult
        }
    }
    return $InstanceResults
}

function Test-TpmProperties
{
    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $tpms = @()
        $InstanceResults = @()
        $sb = {
            $tpm = Get-Tpm
            New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                tpm = $tpm
            }
        }
        if ([string]::IsNullOrEmpty($PsSession))
        {
            $tpms += Invoke-Command -ScriptBlock $sb
        }
        else
        {
            $tpms += Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        foreach ($tpm in $tpms)
        {
            $passed = $false
            $computerName = $tpm.ComputerName
            $desiredPropertyValue = @{
                TpmPresent         = $true #(Get-CimInstance -Namespace root/cimv2/Security/MicrosoftTpm -ClassName Win32_Tpm) is null
                TpmReady           = $true #IsActivated()
                TpmEnabled         = $true #IsEnabled()
                TpmActivated       = $true #IsActivated()
                #TpmOwned = $true #IsOwned()
                #RestartPending = $false #GetPhysicalPresenceRequest()?
                ManagedAuthLevel   = 'Full' #GetOwnerAuth()??
                OwnerClearDisabled = $false #IsOwnerClearDisabled()
                AutoProvisioning   = 'Enabled' #IsAutoProvisioningEnabled()
                LockedOut          = $false #IsLockedOut()
                LockoutCount       = 0 #GetCapLockoutInfo()
            }
            Log-CimData -cimData $tpm -Properties $desiredPropertyValue
            Log-Info -Message ($lhwTxt.TestTpm -f $computerName, $tpm.tpm.ManufacturerIdTxt, $tpm.tpm.ManufacturerVersion)

            # Test properties
            $InstanceResults += foreach ($instance in $tpm.tpm)
            {
                $instanceId = "Machine: $computerName, Class: Tpm, Manufacturer ID: $($tpm.tpm.ManufacturerId)"
                foreach ($propertyName in $desiredPropertyValue.Keys)
                {
                    $detail = $null
                    $passed = $false
                    if ($instance.$propertyName -ne $desiredPropertyValue.$propertyName)
                    {
                        $passed = $false
                        $detail = $lhwTxt.UnexProp -f $propertyName, $instance.$propertyName, $desiredPropertyValue.$propertyName
                        Log-Info -Message $detail -Type Warning
                    }
                    else
                    {
                        $detail = $lhwTxt.Prop -f $propertyName, $instance.$propertyName, $desiredPropertyValue.$propertyName
                        $passed = $true
                    }
                    $status = if ($passed) { 'SUCCESS' } else { 'FAILURE' }
                    $params = @{
                        Name               = 'AzStackHci_Hardware_Test_Tpm_Instance_Properties'
                        Title              = "Test TPM Property $propertyName is $($desiredPropertyValue.$propertyName)"
                        DisplayName        = "Test TPM Property $propertyName is $($desiredPropertyValue.$propertyName) $computerName"
                        Severity           = 'CRITICAL'
                        Description        = "Checking TPM for desired properties"
                        Tags               = @{}
                        Remediation        = 'https://aka.ms/hci-envch'
                        TargetResourceID   = $instanceId
                        TargetResourceName = $instanceId
                        TargetResourceType = 'Tpm'
                        Timestamp          = [datetime]::UtcNow
                        Status             =  $status
                        AdditionalData     = @{
                            Source    = $propertyName
                            Resource  = $instance.$propertyName
                            Detail    = $detail
                            Status    = $status
                            TimeStamp = [datetime]::UtcNow
                        }
                        HealthCheckSource  = $ENV:EnvChkrId
                    }
                    New-AzStackHciResultObject @params
                }
            }
        }
        return $InstanceResults
    }
    catch
    {
        throw $_
    }
}

function Test-TpmCertificates
{
    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $allowedKeyUsage = '2.23.133.8.1' # Endorsement Key Certificate
        $allowedAlgorithms = @(
            '1.2.840.113549.1.1.11' # SHA256
            '1.2.840.113549.1.1.12' # SHA384
            '1.2.840.113549.1.1.13' # SHA512
            '1.2.840.10045.4.3.2' # SHA256ECDSA
            '1.2.840.10045.4.3.3' # SHA384ECDSA
            '1.2.840.10045.4.3.4' # SHA512ECDSA
        )
        $tpmKeys = @()
        $InstanceResults = @()
        $sb = {
            try
            {
                $tpmKeys = Get-TpmEndorsementKeyInfo -ErrorAction SilentlyContinue
            }
            catch {}
            return (New-Object PsObject -Property @{
                ComputerName = $ENV:ComputerName
                tpmKeys = $tpmKeys
            })
        }
        if ([string]::IsNullOrEmpty($PsSession))
        {
            $tpmKeys += Invoke-Command -ScriptBlock $sb
        }
        else
        {
            $tpmKeys += Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        Log-CimData -cimData $tpmKeys
        $InstanceResults += foreach ($tpmKey in $tpmKeys)
        {
            $computerName = $tpmKey.ComputerName
            $tpmCert = $tpmKey.tpmKeys.ManufacturerCertificates + $tpmKey.tpmKeys.AdditionalCertificates
            $instanceId = "Machine: $computerName, Class: TpmCertificates, Subject: $($tpmKey.tpmKeys.ManufacturerCertificates.subject), Thumprint: $($tpmKey.tpmKeys.ManufacturerCertificates.Thumbprint)"

            foreach ($cert in $tpmCert)
            {
                $validCert = $false
                $certDetail = $null
                # Test TPM certificate expiration
                $now = [datetime]::UtcNow
                $sinceIssued = New-TimeSpan -Start $cert.NotBefore -End $now
                $untilExpired = New-TimeSpan -Start $now -End $cert.NotAfter
                $currentCert = $sinceIssued.Days -gt 0 -and $untilExpired.Days -gt 0

                # Test TPM signature algorithm
                $validAlgo = $cert.SignatureAlgorithm.Value -in $allowedAlgorithms

                # Test TPM certificate Enhanced Key Usage
                $validUsage = $cert.EnhancedKeyUsageList.ObjectId -contains $allowedKeyUsage

                # Display certificate properties
                $validCert = $currentCert -and $validAlgo -and $validUsage
                [string[]]$certDetail = "TPM certificate $($cert.Thumbprint), valid = $validCert"
                $certDetail += " Issuer: $($cert.Issuer)"
                $certDetail += " Subject: $($cert.Subject)"
                $certDetail += " Key Usage: $($cert.EnhancedKeyUsageList.FriendlyName -join ', '), valid = $validUsage"
                #$cert.Extensions.Oid.FriendlyName | Foreach-Object { $certDetail += " Extension: $_" }
                $certDetail += " Valid from: $($cert.NotBefore) to $($cert.NotAfter), valid = $currentCert"
                $certDetail += " Algorithm: $($cert.SignatureAlgorithm.FriendlyName), valid = $validAlgo"
                $foundValidCert = $foundValidCert -or $validCert

                $status = if ($validCert) { 'SUCCESS' } else { 'FAILURE' }

                $params = @{
                    Name               = 'AzStackHci_Hardware_Test_Tpm_Certificate_Properties'
                    Title              = "Test TPM Certificate Properties"
                    DisplayName        = "Test TPM $computerName"
                    Severity           = 'CRITICAL'
                    Description        = "Checking TPM for desired properties"
                    Tags               = @{}
                    Remediation        = 'https://aka.ms/hci-envch'
                    TargetResourceID   = $instanceId
                    TargetResourceName = $instanceId
                    TargetResourceType = 'TpmEndorsementKeyInfo'
                    Timestamp          = [datetime]::UtcNow
                    Status             = $status
                    AdditionalData     = @{
                        Source    = $cert.Thumbprint
                        Resource  = "Current: $currentCert. Valid Algorithm: $validAlgo. Valid Key Usage: $validUsage."
                        Detail    = ($certDetail -join "`r")
                        Status    = $status
                        TimeStamp = [datetime]::UtcNow
                    }
                    HealthCheckSource  = $ENV:EnvChkrId
                }
                New-AzStackHciResultObject @params
            }
        }
        return $InstanceResults
    }
    catch
    {
        throw $_
    }
}

function Test-SecureBoot
{
    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $secureBoots = @()
        $sb = {

            if ((Get-Command Confirm-SecureBootUEFI -ErrorAction  SilentlyContinue) -ne $null) {
                <#
                    For devices that Standard hardware security is not supported, this means that the device does not meet
                    at least one of the requirements of standard hardware security.
                    This causes the Confirm-SecureBootUEFI command to fail with the error:
                    Cmdlet not supported on this platform: 0xC0000002
                #>

                try {
                    $secureBoot = Confirm-SecureBootUEFI
                }
                catch {
                    $secureBoot = $false
                }
            }
            else {
                $secureBoot = $false
            }

            New-Object PsObject -Property @{
                SecureBoot = $secureBoot
                ComputerName = $env:COMPUTERNAME
            }
        }
        if ([string]::IsNullOrEmpty($PsSession))
        {
            $secureBoots += Invoke-Command -ScriptBlock $sb
        }
        else
        {
            $secureBoots += Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        Log-CimData -cimData $secureboots
        $InstanceResults = @()
        $InstanceResults += foreach ($SecureBootUEFI in $secureBoots)
        {
            $dtl = $lhwTxt.SecureBoot -f $SecureBootUEFI.SecureBoot, 'True'
            if ($SecureBootUEFI.SecureBoot)
            {
                $status = 'SUCCESS'
            }
            else
            {
                $status = 'FAILURE'
                $dtl = "{0}. {1}" -f $dtl, $lhwTxt.SecureBootNotSupported
            }
            $params = @{
                Name               = 'AzStackHci_Hardware_Test_Secure_Boot'
                Title              = "Test Secure Boot"
                DisplayName        = "Test Secure Boot $($SecureBootUEFI.computerName)"
                Severity           = 'CRITICAL'
                Description        = "Checking Secure Boot"
                Tags               = @{}
                Remediation        = 'https://aka.ms/hci-envch'
                TargetResourceID   = $instanceId
                TargetResourceName = $instanceId
                TargetResourceType = 'SecureBoot'
                Timestamp          = [datetime]::UtcNow
                Status             = $status
                AdditionalData     = @{
                    Source    = $SecureBootUEFI.ComputerName
                    Resource  = $SecureBootUEFI.SecureBoot
                    Detail    = $dtl
                    Status    = $status
                    TimeStamp = [datetime]::UtcNow
                }
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
        return $InstanceResults
    }
    catch
    {
        throw $_
    }
}

function Test-StoragePool
{
    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $StoragePoolsExist = @()
        $sb = {
            New-Object PsObject -Property @{
                StoragePoolExists = [bool](Get-StoragePool -IsPrimordial:$false -ErrorAction SilentlyContinue)
                ComputerName = $ENV:ComputerName
            }
        }
        if ([string]::IsNullOrEmpty($PsSession))
        {
            $StoragePoolsExist += Invoke-Command -ScriptBlock $sb
        }
        else
        {
            $StoragePoolsExist += Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        Log-CimData -cimData $StoragePoolsExist

        $InstanceResults = @()
        $InstanceResults += foreach ($StoragePool in $StoragePoolsExist)
        {
            $status = if (-not $StoragePool.StoragePoolExists) { 'SUCCESS' } else { 'FAILURE' }
            $params = @{
                Name               = 'AzStackHci_Hardware_Test_No_StoragePools'
                Title              = "Test Storage Pools do not exist for new deployment"
                DisplayName        = "Test Storage Pools do not exist for new deployment $($StoragePool.computerName)"
                Severity           = 'CRITICAL'
                Description        = "Checking no storage pools exist for new deployment"
                Tags               = @{}
                Remediation        = 'https://aka.ms/hci-envch'
                TargetResourceID   = $instanceId
                TargetResourceName = $instanceId
                TargetResourceType = 'StoragePool'
                Timestamp          = [datetime]::UtcNow
                Status             = $status
                AdditionalData     = @{
                    Source    = 'StoragePool'
                    Resource  = if ([bool]$StoragePool.StoragePoolExists) { "Present" } else { "Not present" }
                    Detail    = $lhwTxt.StoragePoolFail -f [bool]$StoragePool.StoragePoolExists, 'False'
                    Status    = $status
                    TimeStamp = [datetime]::UtcNow
                }
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
        return $InstanceResults
    }
    catch
    {
        throw $_
    }
}

function Test-FreeSpace
{
    <#
    .SYNOPSIS
        Test free space
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Drive = $env:LocalRootFolderPath,

        [Parameter()]
        [int64]
        $Threshold = 30GB,

        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $cimData = @()
        $InstanceResults = @()
        if ([string]::IsNullOrEmpty($Drive))
        {
            $Drive = "C:\"
        }
        $Drive = (Split-Path -Path $Drive -Qualifier)
        $sb = {
            $DriveExists = Test-Path -Path $args[0] -IsValid
            if ($DriveExists)
            {
                $cimData = Get-CimInstance -ClassName Win32_LogicalDisk -Property DeviceId, FreeSpace | Where-Object DeviceID -EQ $args[0]
            }
            return New-Object PsObject -Property @{
                DriveExists = $DriveExists
                ComputerName = $ENV:ComputerName
                DeviceID = $cimData.DeviceID
                FreeSpace = $cimData.FreeSpace
            }
        }
        if ([string]::IsNullOrEmpty($PsSession))
        {
            $results += Invoke-Command -ScriptBlock $sb -ArgumentList $Drive
        }
        else
        {
            $results += Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList $Drive
        }
        Log-CimData -cimData $results
        $InstanceResults += foreach ($result in $results)
        {
            $computerName = $result.ComputerName
            if ($result.DriveExists -eq $false)
            {
                $status = 'FAILURE'
                $dtl = $lhwtxt.LocalRootFolderPathFail -f $computerName, $Drive
                Log-Info $dtl -Type Warning
            }
            else
            {
                $freeSpaceStr = [int]($result.FreeSpace / 1GB)
                $thresholdStr = [int]($threshold / 1GB)
                $dtl = $lhwtxt.LocalRootFolderPathFreeSpace -f $computerName, $Drive, $freeSpaceStr, $thresholdStr
                if ($result.FreeSpace -gt $Threshold)
                {
                    $status = 'SUCCESS'
                    Log-Info $dtl
                }
                else
                {
                    $status = 'FAILURE'
                    Log-Info $dtl -Type Warning
                }
            }

            $instanceId = "Machine: $computerName, Class: Disk, DriveLetter: $drive"
            $params = @{
                Name               = 'AzStackHci_Hardware_Test_Disk_Space'
                Title              = "Test Disk Space"
                DisplayName        = "Test Disk Space $computerName"
                Severity           = 'WARNING'
                Description        = "Checking Disk Space"
                Tags               = @{}
                Remediation        = 'https://aka.ms/hci-envch'
                TargetResourceID   = $instanceId
                TargetResourceName = $instanceId
                TargetResourceType = 'Disk'
                Timestamp          = [datetime]::UtcNow
                Status             = $status
                AdditionalData     = @{
                    Source    = $computerName
                    Resource  = $cim.DeviceID
                    Detail    = $dtl
                    Status    = $status
                    TimeStamp = [datetime]::UtcNow
                }
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
        return $InstanceResults
    }
    catch
    {
        throw $_
    }
}

function Test-Volume
{
    <#
    .SYNOPSIS
        Test free space
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [string[]]
        $Drive = @('C'),

        # TO DO: Implement Free Space check
        [Parameter()]
        [int64]
        $Threshold = 30GB,

        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $cimData = @()
        $CountResult = @()
        $InstanceCountByGroup = @()

        $sb = {
            $cimData = Get-Volume | Where-Object DriveLetter -in $args
            return $cimData
        }
        if ([string]::IsNullOrEmpty($PsSession))
        {
            $cimData += Invoke-Command -ScriptBlock $sb -ArgumentList $Drive
        }
        else
        {
            $cimData += Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList $Drive
        }

        $groupProperty = @(
            'DriveLetter'
        )

        Log-CimData -cimData $cimData -Properties $groupProperty

        $SystemNames = $cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique
        foreach ($systemName in $SystemNames)
        {
            $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
            #Log-Info -Message ($lhwTxt.VolumeCount -f $systemName, ($Drive -join ','), $sData.Count)
            # Make sure each system has the requisite number of Network Adapters
            $CountResult += Test-Count -CimData $sData -minimum $Drive.Count -ValidatorName 'Hardware' -Severity Critical
        }
        # Make sure each node has the same count by group
        $InstanceCountByGroup += Test-InstanceCountByGroup -CimData $cimData -ValidatorName 'Hardware' -GroupProperty $groupProperty -Severity Critical
        # Finally, the all properties from the $matchProperty array have to be compared for all instances across all nodes.
        return @($CountResult + $InstanceCountByGroup)
    }
    catch
    {
        throw $_
    }
}

function CheckDisksAreAllFlash
{
        param ($cimData)
        # Split disks into type and check each server has the same count
        $allSSD = $cimData | Where-Object {$_.MediaType -match 'SSD|4' -and $_.BusType -match 'SAS|10|SATA|11'}
        $allNVMe = $cimData | Where-Object {$_.MediaType -match 'SSD|4' -and $_.BusType -match 'NVMe|17'}
        $allSCM = $cimData | Where-Object {$_.MediaType -match 'SCM|5'}
        $allHDD = $cimData | Where-Object {$_.MediaType -match 'HDD|3'}
        $className = $CimData.CimSystemProperties.ClassName -split '_' | Select-Object -Last 1
        $instanceId = $CimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique

        Log-Info ($lhwTxt.DisksAreAllFlash -f $instanceId)

        # check if they are all flash
        if ($allSSD -xor $allNVMe -and !$allSCM -and !$allHDD)
        {
            $Status = 'SUCCESS'
            $type = 'Info'
        }
        else
        {
            $Status = 'FAILURE'
            $type = 'CRITICAL'
        }

        $detail = $lhwTxt.DisksAreAllFlashDetail -f $instanceId,("HDD: {0}, SSD:{1}, NVMe:{2}, SCM:{3}" -f [bool]$allHDD, [bool]$allSSD, [bool]$allNVMe, [bool]$allSCM)
        Log-Info $detail -Type $type

        $params = @{
            Name               = 'AzStackHci_Hardware_Test_PhysicalDisk_AllFlash'
            Title              = "Test PhysicalDisks are All Flash"
            DisplayName        = "Test PhysicalDisks are All Flash"
            Severity           = 'CRITICAL'
            Description        = "Checking PhysicalDisks are all flash"
            Tags               = @{}
            Remediation        = 'https://learn.microsoft.com/en-us/windows-server/storage/storage-spaces/storage-spaces-direct-hardware-requirements#minimum-number-of-drives-excludes-boot-drive'
            TargetResourceID   = $instanceId
            TargetResourceName = $instanceId
            TargetResourceType = $className
            Timestamp          = [datetime]::UtcNow
            Status             = $status
            AdditionalData     = @{
                Source    = "$className Drive Type"
                Resource  = "HDD: {0}, SSD:{1}, NVMe:{2}, SCM:{3}" -f [bool]$allHDD, [bool]$allSSD, [bool]$allNVMe, [bool]$allSCM
                Detail    = $detail
                Status    = $status
                TimeStamp = [datetime]::UtcNow
            }
            HealthCheckSource  = $ENV:EnvChkrId
        }
        New-AzStackHciResultObject @params
}

function Test-MinCoreCount
{
    <#
    .SYNOPSIS
        Get minimum core count
    .DESCRIPTION
        Get core count from local machine to use as minimum core count
        Expecting data from PsSessions to include local machine indicating ECE.
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $cimParams = @{
                ClassName = 'Win32_Processor'
                Property  = 'NumberOfCores'
            }
            $cimData = @(Get-CimInstance @cimParams)
            return $cimData
        }
        $cimData = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        else
        {
            Invoke-Command -ScriptBlock $sb
        }

        $instanceResults = @()
        Log-CimData -cimData $cimData -Properties NumberOfCores

        if ((Get-WmiObject -Class Win32_ComputerSystem).Model -eq "Virtual Machine")
        {
            $environmentType = "Virtual"
            $RequiredTotalNumberOfCores = 4
        } else {
            $environmentType = "Physical"
            # Set min cores to local machine
            # This should only apply the scenario where there is a PsSession to all nodes,
            # and one of the nodes is also the local machine i.e. ECE invocation
            $RequiredTotalNumberOfCores = GetTotalNumberOfCores -cimData ($cimData | Where-Object { $_.CimSystemProperties.ServerName -like "$($ENV:COMPUTERNAME)*"})
        }
        Log-Info -Message ($lhwTxt.CoreCountRequirement -f $RequiredTotalNumberOfCores, $environmentType)

        if ($RequiredTotalNumberOfCores)
        {
            [array]$SystemNames = $cimData.CimSystemProperties.ServerName | Where-Object {$PSITEM -notlike "$($ENV:COMPUTERNAME)*"} | Sort-Object | Get-Unique
            $instanceResults += foreach ($systemName in $SystemNames)
            {
                $sData = $CimData | Where-Object { $_.CimSystemProperties.ServerName -eq $systemName }
                $TotalNumberOfCores = GetTotalNumberOfCores -cimData $sData
                if ($TotalNumberOfCores)
                {
                    $detail = $lhwTxt.CheckMinCoreCount -f $SystemName, $TotalNumberOfCores, $RequiredTotalNumberOfCores
                    if ($TotalNumberOfCores -ge $RequiredTotalNumberOfCores)
                    {
                        $status = 'SUCCESS'
                        Log-Info -message $detail
                    }
                    else
                    {
                        $status = 'FAILURE'
                        Log-Info -message $detail -Type Warning
                    }
                }
                else
                {
                    $detail = $lhwTxt.UnexpectedCoreCount -f 'Unavailable','1'
                    $status = 'FAILURE'
                    Log-Info -message $detail -Type Warning
                }

                $params = @{
                    Name               = 'AzStackHci_Hardware_Test_Minimum_CPU_Cores'
                    Title              = 'Test Minimum CPU Cores'
                    DisplayName        = "Test Minimum CPU Cores $systemName"
                    Severity           = 'WARNING'
                    Description        = 'Checking minimum CPU cores'
                    Tags               = @{}
                    Remediation        = 'https://aka.ms/hci-envch'
                    TargetResourceID   = $systemName
                    TargetResourceName = $systemName
                    TargetResourceType = $cimParams.className
                    Timestamp          = [datetime]::UtcNow
                    Status             = $status
                    AdditionalData     = @{
                        Source    = $cimParams.ClassName
                        Resource  = 'Core Count'
                        Detail    = $detail
                        Status    = $status
                        TimeStamp = [datetime]::UtcNow
                    }
                    HealthCheckSource  = $ENV:EnvChkrId
                }
                New-AzStackHciResultObject @params
            }
        }
        else
        {
            Log-info $lhwTxt.SkippedCoreCount -type Warning
        }
        return $instanceResults
    }
    catch
    {
        throw $_
    }
}

function Test-VirtualDisk
{
        <#
    .SYNOPSIS
        Test Virtual Disk
    .DESCRIPTION
        During repair test virtual disk
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            New-Object -Type PsObject -Property @{
                VirtualDiskExists = [bool](Get-StoragePool -IsPrimordial:$false -ErrorAction SilentlyContinue | Get-VirtualDisk)
                ComputerName = $ENV:COMPUTERNAME
            }
        }

        $VirtualDiskExists = @()
        if ([string]::IsNullOrEmpty($PsSession))
        {
            $VirtualDiskExists += Invoke-Command -ScriptBlock $sb
        }
        else
        {
            $VirtualDiskExists += Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        Log-CimData -cimData $VirtualDiskExists
        $instanceResults = @()
        $instanceResults += foreach ($virtualDisk in $VirtualDiskExists)
        {
            if ($virtualDisk.VirtualDiskExists)
            {
                $status = 'SUCCESS'
                $detail = $lhwTxt.VirtualDiskExists -f $virtualDisk.ComputerName
                Log-Info $detail
            }
            else
            {
                $status = 'FAILURE'
                $detail = $lhwTxt.VirtualDiskNotExists -f $virtualDisk.ComputerName
                Log-Info $detail -Type Warning
            }

            $instanceId = "Machine: $($virtualDisk.ComputerName), Class: VirtualDisk"
            $params = @{
                Name               = 'AzStackHci_Hardware_Test_VirtualDisk_Exists'
                Title              = 'Test Virtual Disk exists'
                DisplayName        = "Test Virtual Disk exists $($virtualDisk.ComputerName)"
                Severity           = 'CRITICAL'
                Description        = 'Checking virtual disk(s) exist for repair'
                Tags               = @{}
                Remediation        = 'https://aka.ms/hci-envch'
                TargetResourceID   = $instanceId
                TargetResourceName = $instanceId
                TargetResourceType = 'VirtualDisk'
                Timestamp          = [datetime]::UtcNow
                Status             = $status
                AdditionalData     = @{
                    Source    = $virtualDisk.ComputerName
                    Resource  = if ($virtualDisk.VirtualDiskExists) { "Present" } else { "Not present" }
                    Detail    = $detail
                    Status    = $status
                    TimeStamp = [datetime]::UtcNow
                }
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
    }
    catch
    {
        throw $_
    }
}

function Test-VirtualizationBasedSecurity
{
    <#
    .SYNOPSIS
        Test Virtualization-based Security (VBS)
    .DESCRIPTION
        Test if hardware supports VBS, which is required on HCI
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $vbsMethodDefinition = @'
public enum Values
{
    SecureKernelRunning,
    HvciEnabled,
    HvciStrictMode,
    DebugEnabled,
    FirmwarePageProtection,
    EncryptionKyAvailable,
    SpareFlags,
    TrustletRunning,
    HvciDisableAllowed,
    SpareFlags2,
    Sparce1,
    Sparce2,
    Sparce3,
    Sparce4,
    Sparce5,
    Sparce6
}
 
public enum SYSTEM_INFORMATION_CLASS_EX : uint
{
    SystemBootEnvironmentInformation = 90,
    SystemIsolatedUserModeInformation = 165,
    SystemDmaGuardPolicyInformation = 202
}
 
public struct SYSTEM_ISOLATED_USER_MODE_INFORMATION
{
    public Values Bits;
    public ulong Spare7;
}
 
public static bool GetVBSCapable()
{
    bool capable = false;
    bool enabled = false;
 
    GetVBSInfo(ref capable, ref enabled);
    return capable;
}
 
[DllImport("ntdll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint NtQuerySystemInformation(
  [In] SYSTEM_INFORMATION_CLASS_EX SystemInformationClass,
  [In][Out] IntPtr SystemInformation,
  [In] uint SystemInformationLength,
  [Out] uint ReturnLength);
 
public static void GetVBSInfo(ref bool capable, ref bool enabled)
{
    var outSize = (uint)0;
    var outBuffer = IntPtr.Zero;
 
    try
    {
        outSize = (uint)Marshal.SizeOf(typeof(SYSTEM_ISOLATED_USER_MODE_INFORMATION));
        outBuffer = Marshal.AllocHGlobal((int)outSize);
 
        for (long offset = 0; offset < outSize; offset++)
        {
            Marshal.WriteByte(outBuffer, (int)offset, 0);
        }
 
        uint retValue = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS_EX.SystemIsolatedUserModeInformation, outBuffer, outSize, 0);
        if (retValue != 0)
        {
            throw new Exception(Marshal.GetLastWin32Error().ToString());
        }
 
        SYSTEM_ISOLATED_USER_MODE_INFORMATION iumInfo = new SYSTEM_ISOLATED_USER_MODE_INFORMATION();
        iumInfo = (SYSTEM_ISOLATED_USER_MODE_INFORMATION)Marshal.PtrToStructure(outBuffer, typeof(SYSTEM_ISOLATED_USER_MODE_INFORMATION));
 
        capable = ((int)iumInfo.Bits | 0x01) != 0;
        enabled = ((int)iumInfo.Bits | 0x02) != 0;
    }
    finally
    {
        Marshal.FreeHGlobal(outBuffer);
    }
}
'@

            $null = Add-Type -MemberDefinition $vbsMethodDefinition -Name "VirtualizationBasedSecurity" -Namespace "Microsoft.PowerShell.AzStackHci.EnvironmentChecker.Hardware" -PassThru
            $vbsCapable = [Microsoft.PowerShell.AzStackHci.EnvironmentChecker.Hardware.VirtualizationBasedSecurity]::GetVBSCapable()

            New-Object -Type PsObject -Property @{
                VbsCapable = $vbsCapable
                ComputerName = $ENV:COMPUTERNAME
            }
        }

        $vbsCapabilities = @()
        if ([string]::IsNullOrEmpty($PsSession))
        {
            $vbsCapabilities += Invoke-Command -ScriptBlock $sb
        }
        else
        {
            $vbsCapabilities += Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        Log-CimData -cimData $vbsCapabilities
        $instanceResults = @()
        $instanceResults += foreach ($vbsCapability in $vbsCapabilities)
        {
            if ($vbsCapability.VbsCapable)
            {
                $status = 'SUCCESS'
                $detail = $lhwTxt.VbsCapable -f $vbsCapability.ComputerName
                Log-Info $detail
            }
            else
            {
                $status = 'FAILURE'
                $detail = $lhwTxt.VbsIncapable -f $vbsCapability.ComputerName
                Log-Info $detail -Type Warning
            }

            $instanceId = "Machine: $($vbsCapability.ComputerName), Class: Virtualization-based Security"
            $params = @{
                Name               = 'AzStackHci_Hardware_Test_VirtualizationBasedSecurity'
                Title              = 'Test Virtualization-based Security'
                DisplayName        = "Test Virtualization-based Security $($virtualDisk.ComputerName)"
                Severity           = 'CRITICAL'
                Description        = 'Checking Virtualization-based Security capability'
                Tags               = @{}
                Remediation        = 'https://aka.ms/hci-envch'
                TargetResourceID   = $instanceId
                TargetResourceName = $instanceId
                TargetResourceType = 'Virtualization-based Security'
                Timestamp          = [datetime]::UtcNow
                Status             = $status
                AdditionalData     = @{
                    Source    = $vbsCapability.ComputerName
                    Resource  = if ($vbsCapability.VbsCapable) { "Present" } else { "Not present" }
                    Detail    = $detail
                    Status    = $status
                    TimeStamp = [datetime]::UtcNow
                }
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
        return $InstanceResults
    }
    catch
    {
        throw $_
    }
}

function GetTotalNumberOfCores
{
    <#
    .SYNOPSIS
        Multiply number of cores by number of processors
    .DESCRIPTION
        Multiply number of cores by number of processors
    #>


    param ($cimData)

    try {
        if ($cimData)
        {
            $numberOfCores = $cimData | Select-Object -ExpandProperty NumberOfCores | Sort-Object | Get-Unique
            if ($numberOfCores.count -ne 1)
            {
                throw ($lhwTxt.UnexpectedCoreCount -f $numberOfCores.count, '1')
            }
            else
            {
                return ($numberOfCores * @($cimData).count)
            }
        }
        else
        {
            throw $lhwTxt.NoCoreReference
        }
    }
    catch
    {
        Log-Info ($lhwTxt.UnableCoreCount -f $_) -Type Warning
    }
}

function Test-MountedMedia
{
    <#
    .SYNOPSIS
        Test Mounted Media
    .DESCRIPTION
        Test is any media is mounted on the system such as CD, DVD, etc.
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $sb = {
            $cimParams = @{
                ClassName = 'Win32_CDROMDrive'
                Property  = '*'
            }
            $cimData = @(Get-CimInstance @cimParams)
            New-Object -Type PsObject -Property @{
                MediaExists = [bool]$cimData.MediaLoaded
                cimData = $cimData
                ComputerName = $ENV:COMPUTERNAME
            }
        }

        $remoteOutput = @()
        if ([string]::IsNullOrEmpty($PsSession))
        {
            $remoteOutput += Invoke-Command -ScriptBlock $sb
        }
        else
        {
            $remoteOutput += Invoke-Command -Session $PsSession -ScriptBlock $sb
        }
        Log-CimData -cimData $remoteOutput.CimData
        $instanceResults = @()
        $instanceResults += foreach ($media in $remoteOutput)
        {
            if ($media.MediaExists)
            {
                $status = 'FAILURE'
                $detail = $lhwTxt.MediaExists -f $media.ComputerName, ($media.CimData | % { "Name: $($_.Name), Media: $($_.Caption), Drive: $($_.Drive)" }) -join '. '
                Log-Info $detail -Type CRITICAL
            }
            else
            {
                $status = 'SUCCESS'
                $detail = $lhwTxt.MediaNotExists -f $media.ComputerName
                Log-Info $detail
            }

            $instanceId = "Machine: $($media.ComputerName), Class: CDROMDrive"
            $params = @{
                Name               = 'AzStackHci_Hardware_Test_MountedMedia_Exists'
                Title              = 'Test No Mounted Media exists'
                DisplayName        = "Test No Mounted Media exists $($virtualDisk.ComputerName)"
                Severity           = 'CRITICAL'
                Description        = 'Checking mounted media does not exist'
                Tags               = @{}
                Remediation        = 'https://aka.ms/nomountedmedia'
                TargetResourceID   = $instanceId
                TargetResourceName = $instanceId
                TargetResourceType = 'CDROMDrive'
                Timestamp          = [datetime]::UtcNow
                Status             = $status
                AdditionalData     = @{
                    Source    = $media.ComputerName
                    Resource  = if ($virtualDisk.MediaExists) { "Present" } else { "Not present" }
                    Detail    = $detail
                    Status    = $status
                    TimeStamp = [datetime]::UtcNow
                }
                HealthCheckSource  = $ENV:EnvChkrId
            }
            New-AzStackHciResultObject @params
        }
        return $instanceResults
    }
    catch
    {
        throw $_
    }

}

Export-ModuleMember -Function Test-*
# SIG # Begin signature block
# MIIoRgYJKoZIhvcNAQcCoIIoNzCCKDMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBClZ0DaFUywLaK
# O/+by/pQXecH8cWmyAaY3Z6oNXX6nqCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0
# Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz
# NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo
# DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3
# a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF
# HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy
# 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC
# Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj
# L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp
# h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3
# cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X
# dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL
# E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi
# u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1
# sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq
# 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb
# DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/
# V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGiYwghoiAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIC8Fs4KdwtWli0N3HIvxiRYU
# 5Nk+H4Gp9XvCiCkR55I/MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAeQlZOqif2RepTdeMLfPMoQXvUUJ4d4hVPkQo7sUWQK7b49fIe68d7GQS
# /lS9NYMY6HZ2AjmIwIRqq0OnUo4tz1bcbozagjlpCm7P9M2LAoNjz32yp6RzmneU
# pDzb2RYievEj4i84VkTZG8XT3+XuvNLlgmrBo3M663AJaszWPeAZWkdQhW+P6qdW
# oyZ/cx3HJwwSVVnzlrclEiydtQif9lvI0ZULz45qCYobsn+wQ5iSknB+xiUvKFtv
# 5YSlq7pkxYm+M69dSiW+SpQVF04V/PZw8d0HPNNihBnpuxyIyJ2AXkPEhfKi33Yt
# 3qN9Lfhl7Nq81rR7q75rk7q1A4ld9KGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCC
# F5gGCSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq
# hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCB4SrXxv+6ep4txAtUxKIijXDTBHvAIBZ2gZXY9QlpNIgIGZ0or4OWE
# GBMyMDI0MTIwNDE1MDMzMS41NjZaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# Tjo1MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaCCEf4wggcoMIIFEKADAgECAhMzAAACAAvXqn8bKhdWAAEAAAIAMA0G
# CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0
# MDcyNTE4MzEyMVoXDTI1MTAyMjE4MzEyMVowgdMxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w
# ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjUyMUEt
# MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr1XaadKkP2TkunoTF573
# /tF7KJM9Doiv3ccv26mqnUhmv2DM59ikET4WnRfo5biFIHc6LqrIeqCgT9fT/Gks
# 5VKO90ZQW2avh/PMHnl0kZfX/I5zdVooXHbdUUkPiZfNXszWswmL9UlWo8mzyv9L
# p9TAtw/oXOYTAxdYSqOB5Uzz1Q3A8uCpNlumQNDJGDY6cSn0MlYukXklArChq6l+
# KYrl6r/WnOqXSknABpggSsJ33oL3onmDiN9YUApZwjnNh9M6kDaneSz78/YtD/2p
# Gpx9/LXELoazEUFxhyg4KdmoWGNYwdR7/id81geOER69l5dJv71S/mH+Lxb6L692
# n8uEmAVw6fVvE+c8wjgYZblZCNPAynCnDduRLdk1jswCqjqNc3X/WIzA7GGs4HUS
# 4YIrAUx8H2A94vDNiA8AWa7Z/HSwTCyIgeVbldXYM2BtxMKq3kneRoT27NQ7Y7n8
# ZTaAje7Blfju83spGP/QWYNZ1wYzYVGRyOpdA8Wmxq5V8f5r4HaG9zPcykOyJpRZ
# y+V3RGighFmsCJXAcMziO76HinwCIjImnCFKGJ/IbLjH6J7fJXqRPbg+H6rYLZ8X
# BpmXBFH4PTakZVYxB/P+EQbL5LNw0ZIM+eufxCljV4O+nHkM+zgSx8+07BVZPBKs
# looebsmhIcBO0779kehciYMCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSAJSTavgkj
# Kqge5xQOXn35fXd3OjAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf
# BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww
# bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El
# MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
# BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAKPCG9njRtIqQ
# +fuECgxzWMsQOI3HvW7sV9PmEWCCOWlTuGCIzNi3ibdLZS0b2IDHg0yLrtdVuBi3
# FxVdesIXuzYyofIe/alTBdV4DhijLTXtB7NgOno7G12iO3t6jy1hPSquzGLry/2m
# EZBwIsSoS2D+H+3HCJxPDyhzMFqP+plltPACB/QNwZ7q+HGyZv3v8et+rQYg8sF3
# PTuWeDg3dR/zk1NawJ/dfFCDYlWNeCBCLvNPQBceMYXFRFKhcSUws7mFdIDDhZpx
# qyIKD2WDwFyNIGEezn+nd4kXRupeNEx+eSpJXylRD+1d45hb6PzOIF7BkcPtRtFW
# 2wXgkjLqtTWWlBkvzl2uNfYJ3CPZVaDyMDaaXgO+H6DirsJ4IG9ikId941+mWDej
# kj5aYn9QN6ROfo/HNHg1timwpFoUivqAFu6irWZFw5V+yLr8FLc7nbMa2lFSixzu
# 96zdnDsPImz0c6StbYyhKSlM3uDRi9UWydSKqnEbtJ6Mk+YuxvzprkuWQJYWfpPv
# ug+wTnioykVwc0yRVcsd4xMznnnRtZDGMSUEl9tMVnebYRshwZIyJTsBgLZmHM7q
# 2TFK/X9944SkIqyY22AcuLe0GqoNfASCIcZtzbZ/zP4lT2/N0pDbn2ffAzjZkhI+
# Qrqr983mQZWwZdr3Tk1MYElDThz2D0MwggdxMIIFWaADAgECAhMzAAAAFcXna54C
# m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp
# Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy
# MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51
# yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY
# 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9
# cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN
# 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua
# Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74
# kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2
# K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5
# TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk
# i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q
# BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri
# Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC
# BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl
# pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y
# eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA
# YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw
# MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp
# b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm
# ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM
# 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW
# OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4
# FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw
# xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX
# fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX
# VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC
# onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU
# 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG
# ahC0HVUzWLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# Tjo1MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAjJOfLZb3ivipL3sSLlWFbLrWjmSggYMw
# gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF
# AAIFAOr6mPAwIhgPMjAyNDEyMDQwOTAxMzZaGA8yMDI0MTIwNTA5MDEzNlowdzA9
# BgorBgEEAYRZCgQBMS8wLTAKAgUA6vqY8AIBADAKAgEAAgIbuwIB/zAHAgEAAgIS
# SDAKAgUA6vvqcAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAow
# CAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQBHgNG/1vgC
# NVLC+4luI4uHBRgVj6v2+QIyujWPIQ1dYxsj+nuYd1iFcM8Bey5HGWHIVdgcAfp1
# kCncg7ZB/JYPUisBgmkhM1rFXE8zfj/hE2AM+YLaW1A3/jqlZJoArwhypDJyrCy8
# DpKnF7uzFGxmz96jr1f0cFYNMp827FWQvC+9ZwQo/DBcCq45A+5BfKrzuChDGJxA
# SdVj2VRQQ6VcZiwmP84KxEaM6msBDQOrnL5xZznPHG84rDoRidGGGRqAdzW/4ooV
# TvKjM//RaMxOCXZ8FJTqo423D8+mOd2P4WClH2glk/6TQZ49qaeaYKQzPqKsgLta
# YClDCHdUsQloMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAIAC9eqfxsqF1YAAQAAAgAwDQYJYIZIAWUDBAIBBQCgggFKMBoG
# CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQg2Ez5njrq
# Jj1ZmbLp8aPa3I3wP3A3JLB20yxRfWvbUgcwgfoGCyqGSIb3DQEJEAIvMYHqMIHn
# MIHkMIG9BCDUyO3sNZ3burBNDGUCV4NfM2gH4aWuRudIk/9KAk/ZJzCBmDCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACAAvXqn8bKhdWAAEA
# AAIAMCIEIMaU6J1dp6uAu7CgFTMZBBbh4Qo+kpwMyZSFgys4/zMDMA0GCSqGSIb3
# DQEBCwUABIICABOS4qMIb/RrkjWQNGLPpgH763yaGzN9I4yRYlLgAXRNQz0FVVdo
# cyHtpf8CX+o/YxFJfB0W9hll4fxZFPPUqrQZjq+NWzp0HiGTT4Ix6pI7wj7ltry7
# 1EL7J51DZHGmt7a+uaDF6QjAbcgHO4EYKVh1QbBFoDr+ZubT3BFGzZY8hVKm538g
# ISnOFcXwz7N/ZCRCqRJgf0Equ9JCG3NOdj2qYyC++kX6Ku9xs3qFRzzNnT8mWpLh
# p93avLkpw1gxRwGLcGrGXJI2DSPGG5cQh/dC9cgGsbnZrUBOTJzfSR4SJ4tF3AoT
# jAk9sT5Ig9oyTnBw08wuKcDQQ4KirYYe7d39G0SzHBVtRvHLQ5r02EXxD1aZXjOC
# LwpydcmvwcqftH8toIrpjaoNKEP8XfZdz6/Nf4RyAE3h+AVWscWDxTiGtHKirH3D
# nvNQL7xrAko7W+9/hx87o10SVNtDT4ygjeWfEr8KTZDHRfikmhhoksBhhjB9dW01
# 95+Bxj1IU8Q1HRQk7iKe1U0QDRaP1xqa1+5rT4RK1DHl8DK/YyEe+RGndmsZU8bf
# v8Jt5JWzv+P380BCjSAgcxIJHpt+L/BgKYOhWwplc4+502egB932s30exvZFN65P
# tJM6joEVTuunYTk1RY+MS/3FIWZi0aYUTgaHmEPLBVhD/GLJlWy3MR6N
# SIG # End signature block