Private/Kinds/Temperature.ps1

# The Temperature Kind: how hot the machine is when it is doing nothing.
#
# A machine already warm at rest has nothing left for load, which is what a Customer
# experiences as a laptop that is fine in the morning and unusable by eleven. The reading
# is an ACPI thermal zone rather than a core sensor, so it is reported with that caveat
# attached rather than presented as the CPU temperature it is not.

$script:TemperatureNeedsAdmin = $true

# ACPI reports tenths of a kelvin. A zone that answers outside this range is answering
# with something that is not a temperature, which several firmwares do.
$script:TemperaturePlausibleMinC = 0
$script:TemperaturePlausibleMaxC = 130

function Get-TemperatureData {
    [CmdletBinding()]
    [OutputType([psobject])]
    param([hashtable]$Parameters = @{})

    $zones = @()
    try {
        $zones = @(Get-CimInstance -Namespace root/wmi -ClassName MSAcpi_ThermalZoneTemperature -ErrorAction Stop |
            ForEach-Object {
                [pscustomobject]@{
                    InstanceName = "$($_.InstanceName)"
                    # Converted but not filtered: whether a reading is plausible is a
                    # judgement, and a Gatherer that dropped one would hide a broken sensor.
                    CelsiusValue = [math]::Round(($_.CurrentTemperature / 10 - 273.15), 1)
                }
            })
    }
    catch { }

    [pscustomobject]@{
        PSTypeName   = 'Gutcheck.Data.Temperature'
        ThermalZones = $zones
        Elevated     = (Get-CurrentPrivilege) -eq 'admin'
    }
}

function ConvertTo-TemperatureFinding {
    [CmdletBinding()]
    [OutputType([psobject])]
    param(
        [AllowNull()]$Data,
        [hashtable]$Parameters = @{}
    )

    $warn = Get-Parameter $Parameters 'TemperatureAtRestWarnAboveC' 70
    $fail = Get-Parameter $Parameters 'TemperatureAtRestFailAboveC' 85

    $readings = @((Get-DataCollection $Data 'ThermalZones') |
        ForEach-Object { ConvertTo-Number $_.CelsiusValue } |
        Where-Object { $null -ne $_ -and
                       $_ -gt $script:TemperaturePlausibleMinC -and
                       $_ -lt $script:TemperaturePlausibleMaxC })

    if (-not $readings.Count) {
        $elevated = [bool](Get-DataProperty $Data 'Elevated')
        return New-Finding -Category CPU -Check (Get-Text 'Check.Temperature.Temperature') -Severity INFO `
            -Value $(if ($elevated) { (Get-Text 'Value.Temperature.NoAcpiThermalZone') }
                     else { (Get-Text 'Value.Shared.NeedsAdmin') }) `
            -Hint $(if ($elevated) {
                'This model does not expose a thermal zone; use the vendor''s own tool if temperature matters here'
            } else {
                'Rerun and allow the admin prompt: without it this machine''s temperature is unknown, not fine'
            })
    }

    # The hottest zone, because one zone at 95 C is not excused by three at 40.
    $hottest = ($readings | Measure-Object -Maximum).Maximum

    # The caveat belongs in the Value rather than the Hint. It qualifies the reading, not
    # what to do about it, and a Hint is dropped from an OK Finding - which is exactly the
    # Finding a Technician is most likely to quote at somebody as a core temperature.
    New-Finding -Category CPU -Check (Get-Text 'Check.Temperature.ACPIThermalZoneAt') `
        -Severity (Get-Severity $hottest $warn $fail) `
        -Value ((Get-Text 'Value.Temperature.AcpiZone') -f $hottest) `
        -Hint (Get-Text 'Hint.Temperature.HotAtRestMeansNothing')
}