Public/Get-EFEndpointHealth.ps1

function Get-EFEndpointHealth {
    <#
    .SYNOPSIS
    Produces an operational health summary for the local Windows endpoint.
 
    .DESCRIPTION
    Combines inventory and pending-reboot signals into a compact health report suitable
    for RMM, Intune proactive remediation, scheduled tasks, and monitoring ingestion.
    Status is Healthy, Warning, Critical, or Unknown. Unknown means one or more
    required health signals could not be collected and no known warning or critical
    issue outranked that uncertainty. ExitCode is 0 only for a complete Healthy
    result, 1 for Warning, 2 for Critical, and 3 for Unknown.
 
    .PARAMETER MinimumFreeSpacePercent
    The system-drive free-space percentage below which a warning is reported.
 
    .PARAMETER MaximumUptimeDays
    The uptime threshold after which a pending maintenance warning is reported.
 
    .PARAMETER IncludeSoftware
    Includes installed software in the nested Inventory object.
 
    .PARAMETER NoProgress
    Suppresses the progress display for non-interactive automation hosts.
 
    .EXAMPLE
    Get-EFEndpointHealth -MinimumFreeSpacePercent 15 -MaximumUptimeDays 30
 
    .OUTPUTS
    EndpointForge.EndpointHealth
    #>

    [CmdletBinding()]
    [OutputType('EndpointForge.EndpointHealth')]
    param(
        [ValidateRange(1, 99)]
        [int]$MinimumFreeSpacePercent = 15,

        [ValidateRange(1, 3650)]
        [int]$MaximumUptimeDays = 30,

        [switch]$IncludeSoftware,

        [switch]$NoProgress
    )

    $null = Test-EFWindows -Throw
    $inventory = Get-EFEndpointInventory -IncludeSoftware:$IncludeSoftware -NoProgress:$NoProgress
    $pendingReboot = Get-EFPendingReboot
    $checks = [Collections.Generic.List[object]]::new()

    $freePercent = Get-EFPropertyValue -InputObject $inventory.SystemDrive -Name 'FreeSpacePercent'
    if ($null -eq $freePercent) {
        $checks.Add([pscustomobject]@{
            Id = 'DiskFreeSpace'; Severity = 'Information'; Status = 'Unknown'
            Message = 'System drive free space could not be collected.'
            ActualValue = $null; Threshold = $MinimumFreeSpacePercent
        })
    }
    elseif ([double]$freePercent -lt $MinimumFreeSpacePercent) {
        $severity = if ([double]$freePercent -lt [math]::Max(5, $MinimumFreeSpacePercent / 2)) { 'Critical' } else { 'Warning' }
        $checks.Add([pscustomobject]@{
            Id = 'DiskFreeSpace'; Severity = $severity; Status = 'Unhealthy'
            Message = "System drive free space is $freePercent%; threshold is $MinimumFreeSpacePercent%."
            ActualValue = $freePercent; Threshold = $MinimumFreeSpacePercent
        })
    }
    else {
        $checks.Add([pscustomobject]@{
            Id = 'DiskFreeSpace'; Severity = 'Information'; Status = 'Healthy'
            Message = 'System drive free space is within threshold.'
            ActualValue = $freePercent; Threshold = $MinimumFreeSpacePercent
        })
    }

    $uptimeDays = Get-EFPropertyValue -InputObject $inventory.OperatingSystem -Name 'UptimeDays'
    if ($null -eq $uptimeDays) {
        $checks.Add([pscustomobject]@{
            Id = 'Uptime'; Severity = 'Information'; Status = 'Unknown'
            Message = 'Endpoint uptime could not be collected.'
            ActualValue = $null; Threshold = $MaximumUptimeDays
        })
    }
    else {
        $uptimeHealthy = [double]$uptimeDays -le $MaximumUptimeDays
        $checks.Add([pscustomobject]@{
            Id = 'Uptime'; Severity = if ($uptimeHealthy) { 'Information' } else { 'Warning' }
            Status = if ($uptimeHealthy) { 'Healthy' } else { 'Unhealthy' }
            Message = if ($uptimeHealthy) { 'Endpoint uptime is within threshold.' } else { "Endpoint uptime is $uptimeDays days; threshold is $MaximumUptimeDays days." }
            ActualValue = $uptimeDays; Threshold = $MaximumUptimeDays
        })
    }

    $pendingRebootErrorCount = [int](Get-EFPropertyValue -InputObject $pendingReboot -Name 'ErrorCount' -Default 0)
    $checks.Add([pscustomobject]@{
        Id = 'PendingReboot'
        Severity = if ($pendingReboot.IsRebootPending -or $pendingRebootErrorCount -gt 0) { 'Warning' } else { 'Information' }
        Status = if ($pendingReboot.IsRebootPending) { 'Unhealthy' } elseif ($pendingRebootErrorCount -gt 0) { 'Unknown' } else { 'Healthy' }
        Message = if ($pendingReboot.IsRebootPending -and $pendingRebootErrorCount -gt 0) {
            'Windows confirmed that a restart is pending, although it could not read every other restart indicator.'
        }
        elseif ($pendingRebootErrorCount -gt 0) {
            'Windows could not read every pending-restart indicator, so EndpointForge did not assume that no restart is needed.'
        }
        elseif ($pendingReboot.IsRebootPending) { "Restart pending: $($pendingReboot.Reasons -join ', ')." }
        else { 'No pending restart was detected.' }
        ActualValue = if ($pendingReboot.IsRebootPending) { $true } elseif ($pendingRebootErrorCount -gt 0) { $null } else { $false }
        Threshold = $false
    })

    foreach ($firewallProfile in @($inventory.Security.Firewall)) {
        $enabled = [bool](Get-EFPropertyValue -InputObject $firewallProfile -Name 'Enabled')
        $profileName = [string](Get-EFPropertyValue -InputObject $firewallProfile -Name 'Name')
        $checks.Add([pscustomobject]@{
            Id = "Firewall$profileName"; Severity = if ($enabled) { 'Information' } else { 'Critical' }
            Status = if ($enabled) { 'Healthy' } else { 'Unhealthy' }
            Message = if ($enabled) { "$profileName firewall profile is enabled." } else { "$profileName firewall profile is disabled." }
            ActualValue = $enabled; Threshold = $true
        })
    }

    $defender = $inventory.Security.Defender
    if ($null -ne $defender) {
        $realtimeEnabled = [bool](Get-EFPropertyValue -InputObject $defender -Name 'RealTimeProtectionEnabled')
        $checks.Add([pscustomobject]@{
            Id = 'DefenderRealTimeProtection'; Severity = if ($realtimeEnabled) { 'Information' } else { 'Critical' }
            Status = if ($realtimeEnabled) { 'Healthy' } else { 'Unhealthy' }
            Message = if ($realtimeEnabled) { 'Microsoft Defender real-time protection is enabled.' } else { 'Microsoft Defender real-time protection is disabled.' }
            ActualValue = $realtimeEnabled; Threshold = $true
        })
    }

    $bitLocker = $inventory.Security.BitLocker
    if ($null -ne $bitLocker) {
        $protected = [string](Get-EFPropertyValue -InputObject $bitLocker -Name 'ProtectionStatus') -eq 'On'
        $checks.Add([pscustomobject]@{
            Id = 'BitLockerProtection'; Severity = if ($protected) { 'Information' } else { 'Warning' }
            Status = if ($protected) { 'Healthy' } else { 'Unhealthy' }
            Message = if ($protected) { 'BitLocker protection is on for the system drive.' } else { 'BitLocker protection is not on for the system drive.' }
            ActualValue = Get-EFPropertyValue -InputObject $bitLocker -Name 'ProtectionStatus'; Threshold = 'On'
        })
    }

    foreach ($inventoryError in @($inventory.Errors)) {
        $checks.Add([pscustomobject]@{
            Id = 'InventoryCollection'; Severity = 'Information'; Status = 'Unknown'
            Message = [string]$inventoryError; ActualValue = $null; Threshold = $null
        })
    }

    $criticalCount = @($checks | Where-Object {
        $_.Status -eq 'Unhealthy' -and $_.Severity -eq 'Critical'
    }).Count
    $warningCount = @($checks | Where-Object {
        $_.Status -eq 'Unhealthy' -and $_.Severity -eq 'Warning'
    }).Count
    $unknownCount = @($checks | Where-Object Status -eq 'Unknown').Count
    $knownCount = $checks.Count - $unknownCount
    $coveragePercent = if ($checks.Count -eq 0) { 0 } else { [math]::Round(($knownCount / $checks.Count) * 100, 1) }
    $dataStatus = if ($unknownCount -eq 0) { 'Complete' } elseif ($knownCount -eq 0) { 'Failed' } else { 'Partial' }
    $status = if ($criticalCount -gt 0) {
        'Critical'
    }
    elseif ($warningCount -gt 0) {
        'Warning'
    }
    elseif ($unknownCount -gt 0) {
        'Unknown'
    }
    else {
        'Healthy'
    }
    $exitCode = if ($criticalCount -gt 0) {
        2
    }
    elseif ($warningCount -gt 0) {
        1
    }
    elseif ($unknownCount -gt 0) {
        3
    }
    else {
        0
    }
    $score = [math]::Max(0, 100 - ($criticalCount * 25) - ($warningCount * 10))
    $summaryText = switch ($status) {
        'Healthy' { 'No operational health issues were detected.' }
        'Warning' { "$warningCount warning(s) require attention." }
        'Critical' { "$criticalCount critical issue(s) and $warningCount warning(s) require attention." }
        'Unknown' { "$unknownCount health signal(s) could not be checked, so EndpointForge did not report this endpoint as healthy." }
    }
    $nextStep = if ($status -eq 'Healthy') {
        'Run Get-EFEndpointSummary for one plain-language health and Windows settings checkup.'
    }
    else {
        'Review the Checks property or run Show-EFEndpointSummary -Detailed.'
    }

    [pscustomobject]@{
        PSTypeName      = 'EndpointForge.EndpointHealth'
        ComputerName    = $env:COMPUTERNAME
        CheckedAtUtc    = [DateTime]::UtcNow
        Status          = $status
        Score           = $score
        ExitCode        = $exitCode
        Summary         = $summaryText
        NextStep        = $nextStep
        CriticalCount   = $criticalCount
        WarningCount    = $warningCount
        UnknownCount    = $unknownCount
        DataStatus      = $dataStatus
        CoveragePercent = $coveragePercent
        CollectionErrors = @($inventory.Errors)
        Checks          = @($checks)
        Inventory       = $inventory
        PendingReboot   = $pendingReboot
    }
}