Private/Kinds/System.ps1

# The System Kind: what the machine is.
#
# Two Checks here are about age rather than a reading, so the Gatherer records when it
# looked. The Judge computes the age from that rather than from the clock, which keeps it
# pure and lets a fixture pin an age exactly.

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

    $computer = Get-CimInstance Win32_ComputerSystem  -ErrorAction SilentlyContinue
    $os       = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
    $bios     = Get-CimInstance Win32_BIOS            -ErrorAction SilentlyContinue
    $cpu      = @(Get-CimInstance Win32_Processor     -ErrorAction SilentlyContinue)[0]
    $current  = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction SilentlyContinue
    $power    = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power' -ErrorAction SilentlyContinue

    $modules = @(Get-CimInstance Win32_PhysicalMemory -ErrorAction SilentlyContinue | ForEach-Object {
        [pscustomobject]@{ CapacityBytes = $_.Capacity; ConfiguredClockSpeed = $_.ConfiguredClockSpeed }
    })

    [pscustomobject]@{
        PSTypeName               = 'Gutcheck.Data.System'
        GatheredAt               = Get-Date
        Manufacturer             = $computer.Manufacturer
        Model                    = $computer.Model
        SerialNumber             = $bios.SerialNumber
        OsCaption                = $os.Caption
        OsDisplayVersion         = $current.DisplayVersion
        OsBuildNumber            = $os.BuildNumber
        OsUpdateBuildRevision    = $current.UBR
        BiosVersion              = $bios.SMBIOSBIOSVersion
        BiosReleaseDate          = $bios.ReleaseDate
        ProcessorName            = $cpu.Name
        ProcessorCores           = $cpu.NumberOfCores
        ProcessorLogicalCount    = $cpu.NumberOfLogicalProcessors
        TotalPhysicalMemoryBytes = $computer.TotalPhysicalMemory
        MemoryModules            = $modules
        LastBootUpTime           = $os.LastBootUpTime
        FastStartupEnabled       = $power.HiberbootEnabled
    }
}

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

    New-ModelFinding           -Data $Data -Parameters $Parameters
    New-OperatingSystemFinding -Data $Data -Parameters $Parameters
    New-BiosFinding            -Data $Data -Parameters $Parameters
    New-ProcessorFinding       -Data $Data -Parameters $Parameters
    New-RamInstalledFinding    -Data $Data -Parameters $Parameters
    New-RamModuleFinding       -Data $Data -Parameters $Parameters
    New-UptimeFinding          -Data $Data -Parameters $Parameters
}

function New-ModelFinding {
    [CmdletBinding()]
    param([AllowNull()]$Data, [hashtable]$Parameters)

    $manufacturer = Get-DataProperty $Data 'Manufacturer'
    $model        = Get-DataProperty $Data 'Model'
    if (-not $manufacturer -and -not $model) {
        return New-UnavailableFinding -Category System -Check (Get-Text 'Check.System.Model') `
            -Hint (Get-Text 'Hint.System.TheMachineDidNotReport4')
    }
    New-Finding -Category System -Check (Get-Text 'Check.System.Model') -Severity INFO `
        -Value ('{0} {1} (S/N {2})' -f $manufacturer, $model, (Get-DataProperty $Data 'SerialNumber'))
}

function New-OperatingSystemFinding {
    [CmdletBinding()]
    param([AllowNull()]$Data, [hashtable]$Parameters)

    $caption = Get-DataProperty $Data 'OsCaption'
    if (-not $caption) {
        return New-UnavailableFinding -Category System -Check (Get-Text 'Check.System.OS') `
            -Hint (Get-Text 'Hint.System.TheOperatingSystemDidNot')
    }
    New-Finding -Category System -Check (Get-Text 'Check.System.OS') -Severity INFO -Value ((Get-Text 'Value.System.OperatingSystem') -f
        $caption, (Get-DataProperty $Data 'OsDisplayVersion'),
        (Get-DataProperty $Data 'OsBuildNumber'), (Get-DataProperty $Data 'OsUpdateBuildRevision'))
}

function New-BiosFinding {
    [CmdletBinding()]
    param([AllowNull()]$Data, [hashtable]$Parameters)

    $warnDays = Get-Parameter $Parameters 'BiosAgeWarnDays' 730
    $failDays = Get-Parameter $Parameters 'BiosAgeFailDays' ([double]::MaxValue)

    $released = Get-DataProperty $Data 'BiosReleaseDate'
    if (-not $released) {
        return New-UnavailableFinding -Category System -Check (Get-Text 'Check.System.BIOS') `
            -Hint (Get-Text 'Hint.System.TheMachineDidNotReport')
    }

    $ageDays = ((Get-DataProperty $Data 'GatheredAt') - [datetime]$released).TotalDays
    New-Finding -Category System -Check (Get-Text 'Check.System.BIOS') -Severity (Get-Severity $ageDays $warnDays $failDays) `
        -Value ('{0} ({1:yyyy-MM-dd})' -f (Get-DataProperty $Data 'BiosVersion'), [datetime]$released) `
        -Hint (Get-Text 'Hint.System.BIOSOlderThan2Years')
}

function New-ProcessorFinding {
    [CmdletBinding()]
    param([AllowNull()]$Data, [hashtable]$Parameters)

    $name = Get-DataProperty $Data 'ProcessorName'
    if (-not $name) {
        return New-UnavailableFinding -Category System -Check (Get-Text 'Check.System.CPU') `
            -Hint (Get-Text 'Hint.System.TheMachineDidNotReport5')
    }
    New-Finding -Category System -Check (Get-Text 'Check.System.CPU') -Severity INFO -Value ((Get-Text 'Value.System.Processor') -f
        ([string]$name).Trim(), (Get-DataProperty $Data 'ProcessorCores'), (Get-DataProperty $Data 'ProcessorLogicalCount'))
}

function New-RamInstalledFinding {
    [CmdletBinding()]
    param([AllowNull()]$Data, [hashtable]$Parameters)

    # Graded downwards: here it is the small number that is the problem.
    $warnBelow = Get-Parameter $Parameters 'RamInstalledWarnBelowGB' 15
    $failBelow = Get-Parameter $Parameters 'RamInstalledFailBelowGB' 7.5

    $bytes = ConvertTo-Number (Get-DataProperty $Data 'TotalPhysicalMemoryBytes')
    if ($null -eq $bytes -or $bytes -le 0) {
        return New-UnavailableFinding -Category System -Check (Get-Text 'Check.System.RAMInstalled') `
            -Hint (Get-Text 'Hint.System.TheMachineDidNotReport2')
    }

    $gb = [math]::Round($bytes / 1GB, 1)
    New-Finding -Category System -Check (Get-Text 'Check.System.RAMInstalled') -Severity (Get-SeverityBelow $gb $warnBelow $failBelow) `
        -Value "$gb GB" `
        -Hint (Get-Text 'Hint.System.OfficeTeamsBrowserBusinessApps')
}

function New-RamModuleFinding {
    [CmdletBinding()]
    param([AllowNull()]$Data, [hashtable]$Parameters)

    $modules = (Get-DataCollection $Data 'MemoryModules')
    if (-not $modules.Count) {
        return New-UnavailableFinding -Category System -Check (Get-Text 'Check.System.RAMModules') `
            -Hint (Get-Text 'Hint.System.TheMachineDidNotReport3')
    }

    # A single module means single-channel, which is a real performance finding, but the
    # script reported it as INFO with a Hint rather than a WARN. Carried across unchanged.
    $hint = ''
    if ($modules.Count -eq 1) {
        $hint = (Get-Text 'Hint.System.SingleChannelMemory')
    }

    # Not a bare division: gathered data also reaches a Judge through a CliXML round trip,
    # where a capacity can arrive as text and dividing it would throw rather than report.
    $described = $modules | ForEach-Object {
        $capacity = ConvertTo-Number $_.CapacityBytes
        if ($null -eq $capacity) { '? GB @ {0} MHz' -f $_.ConfiguredClockSpeed }
        else { '{0} GB @ {1} MHz' -f ($capacity / 1GB), $_.ConfiguredClockSpeed }
    }
    New-Finding -Category System -Check (Get-Text 'Check.System.RAMModules') -Severity INFO -Value ($described -join ', ') -Hint $hint
}

function New-UptimeFinding {
    [CmdletBinding()]
    param([AllowNull()]$Data, [hashtable]$Parameters)

    $warnDays = Get-Parameter $Parameters 'UptimeWarnDays' 7
    $failDays = Get-Parameter $Parameters 'UptimeFailDays' ([double]::MaxValue)

    $booted = Get-DataProperty $Data 'LastBootUpTime'
    if (-not $booted) {
        return New-UnavailableFinding -Category System -Check (Get-Text 'Check.System.Uptime') `
            -Hint (Get-Text 'Hint.System.TheOperatingSystemDidNot2')
    }

    $days = ((Get-DataProperty $Data 'GatheredAt') - [datetime]$booted).TotalDays
    $fastStartup = Get-Text 'Value.System.FastStartupOff'
    if ((ConvertTo-Number (Get-DataProperty $Data 'FastStartupEnabled')) -eq 1) { $fastStartup = Get-Text 'Value.System.FastStartupOn' }

    New-Finding -Category System -Check (Get-Text 'Check.System.Uptime') -Severity (Get-Severity $days $warnDays $failDays) `
        -Value ((Get-Text 'Value.System.Uptime') -f $days, $fastStartup) `
        -Hint (Get-Text 'Hint.System.ShutDownWithFastStartup')
}