Public/Get-EFFleetSummary.ps1

function Get-EFFleetSummary {
    <#
    .SYNOPSIS
    Checks several Windows computers without changing them.
 
    .DESCRIPTION
    Runs the same EndpointForge computer checkup on each named computer and returns one
    combined result. This command is read-only: it never installs EndpointForge, turns on
    remote management, changes a Windows setting, or runs a fix.
 
    Each remote computer must already allow PowerShell remoting and must already have
    EndpointForge 0.4.0 or later installed. Your account must have permission to connect.
    These requirements are intentionally not changed for you.
 
    EndpointForge calls its list of expected Windows settings a baseline in scripts. In
    the menu and documentation, that is described simply as a checklist.
 
    .PARAMETER ComputerName
    One or more computer names to check. Duplicate names are checked once.
 
    .PARAMETER Baseline
    The checklist to use. Supply the built-in checklist name, a checklist JSON file, or a
    checklist object. The default is EnterpriseRecommended.
 
    .PARAMETER Credential
    An optional account that already has permission to connect. The credential is used
    only for the remote connection and is not included in the returned report.
 
    .PARAMETER ThrottleLimit
    The largest number of remote checks that may run at the same time.
 
    .PARAMETER IncludeSoftware
    Also collects installed-software details. This makes the report larger and can take
    longer. It still does not change a computer.
 
    .PARAMETER MinimumFreeSpacePercent
    The system-drive free-space level that should produce a warning.
 
    .PARAMETER MaximumUptimeDays
    The number of days running without a restart that should produce a warning.
 
    .EXAMPLE
    Get-EFFleetSummary -ComputerName PC-101,PC-102
 
    Checks two computers with the built-in checklist and returns one combined result.
 
    .EXAMPLE
    Get-EFFleetSummary -ComputerName (Get-Content .\computers.txt) -Credential (Get-Credential)
 
    Checks names from a text file using an account that already has remote access.
 
    .OUTPUTS
    EndpointForge.FleetSummary
 
    .LINK
    Get-EFEndpointSummary
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName,

        [Parameter(Position = 1)]
        [AllowNull()]
        [object]$Baseline = 'EnterpriseRecommended',

        [pscredential]$Credential,

        [ValidateRange(1, 128)]
        [int]$ThrottleLimit = 16,

        [switch]$IncludeSoftware,

        [ValidateRange(1, 99)]
        [int]$MinimumFreeSpacePercent = 15,

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

    $targets = [Collections.Generic.List[string]]::new()
    $seenTargets = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
    foreach ($name in $ComputerName) {
        $trimmedName = [string]$name
        if ($null -ne $trimmedName) { $trimmedName = $trimmedName.Trim() }
        if ([string]::IsNullOrWhiteSpace($trimmedName)) {
            throw [System.ArgumentException]::new('ComputerName cannot contain an empty value.')
        }
        if ($seenTargets.Add($trimmedName)) {
            $targets.Add($trimmedName)
        }
    }
    if ($targets.Count -eq 0) {
        throw [System.ArgumentException]::new('Provide at least one computer name.')
    }

    $resolvedBaseline = Resolve-EFBaseline -Baseline $Baseline
    $startedAtUtc = [DateTime]::UtcNow
    $remoteScript = {
        param($Checklist, $CollectSoftware, $FreeSpaceThreshold, $UptimeThreshold)

        Import-Module EndpointForge -MinimumVersion 0.4.0 -Force -ErrorAction Stop
        $parameters = @{
            Baseline                = $Checklist
            IncludeSoftware         = [bool]$CollectSoftware
            MinimumFreeSpacePercent = [int]$FreeSpaceThreshold
            MaximumUptimeDays       = [int]$UptimeThreshold
            NoProgress              = $true
        }
        $checkup = Get-EFEndpointSummary @parameters
        [pscustomobject]@{
            RemoteComputerName = $env:COMPUTERNAME
            Checkup            = $checkup
        }
    }

    $invokeParameters = @{
        ComputerName  = $targets.ToArray()
        ScriptBlock   = $remoteScript
        ArgumentList  = @($resolvedBaseline, [bool]$IncludeSoftware, $MinimumFreeSpacePercent, $MaximumUptimeDays)
        ThrottleLimit = $ThrottleLimit
        ErrorAction   = 'SilentlyContinue'
        ErrorVariable = 'fleetRemoteErrors'
    }
    if ($PSBoundParameters.ContainsKey('Credential')) {
        $invokeParameters.Credential = $Credential
    }

    $fleetRemoteErrors = @()
    $responses = @()
    try {
        $responses = @(Invoke-Command @invokeParameters)
    }
    catch {
        $fleetRemoteErrors += $_
    }

    $resultList = [Collections.Generic.List[object]]::new()
    $completedTargets = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
    foreach ($response in $responses) {
        if ($null -eq $response) { continue }
        $remoteTarget = [string](Get-EFPropertyValue -InputObject $response -Name 'PSComputerName' -Default '')
        if ([string]::IsNullOrWhiteSpace($remoteTarget)) {
            $remoteTarget = [string](Get-EFPropertyValue -InputObject $response -Name 'RemoteComputerName' -Default '')
        }
        $checkup = Get-EFPropertyValue -InputObject $response -Name 'Checkup'
        if ($null -eq $checkup) { continue }

        $requestedTarget = @($targets | Where-Object { $_ -ieq $remoteTarget } | Select-Object -First 1)
        if ($requestedTarget.Count -eq 0) { $requestedTarget = @($remoteTarget) }
        $requestedName = [string]$requestedTarget[0]
        if (-not [string]::IsNullOrWhiteSpace($requestedName)) {
            $null = $completedTargets.Add($requestedName)
        }
        $resultList.Add([pscustomobject]@{
            PSTypeName            = 'EndpointForge.FleetComputerResult'
            RequestedComputerName = $requestedName
            ComputerName          = [string](Get-EFPropertyValue -InputObject $checkup -Name 'ComputerName' -Default $remoteTarget)
            OverallStatus         = [string](Get-EFPropertyValue -InputObject $checkup -Name 'OverallStatus' -Default 'Incomplete')
            Score                 = Get-EFPropertyValue -InputObject $checkup -Name 'Score'
            IssueCount            = [int](Get-EFPropertyValue -InputObject $checkup -Name 'IssueCount' -Default 0)
            UnknownCount          = [int](Get-EFPropertyValue -InputObject $checkup -Name 'UnknownCount' -Default 0)
            CompletedAtUtc        = Get-EFPropertyValue -InputObject $checkup -Name 'CompletedAtUtc'
            NextStep              = [string](Get-EFPropertyValue -InputObject $checkup -Name 'NextStep' -Default '')
            Checkup               = $checkup
        })
    }

    $failureList = [Collections.Generic.List[object]]::new()
    foreach ($target in $targets) {
        if ($completedTargets.Contains($target)) { continue }

        $matchingErrors = @($fleetRemoteErrors | Where-Object {
            $errorTarget = ''
            if ($null -ne $_.OriginInfo) { $errorTarget = [string]$_.OriginInfo.PSComputerName }
            if ([string]::IsNullOrWhiteSpace($errorTarget)) { $errorTarget = [string]$_.TargetObject }
            $errorTarget -ieq $target -or [string]$_.Exception.Message -match [regex]::Escape($target)
        })
        $message = if ($matchingErrors.Count -gt 0) {
            [string]$matchingErrors[0].Exception.Message
        }
        else {
            'The computer did not return a checkup. Confirm its name, network access, PowerShell remoting permission, and that EndpointForge 0.4.0 or later is already installed there.'
        }
        $failureList.Add([pscustomobject]@{
            PSTypeName   = 'EndpointForge.FleetFailure'
            ComputerName = [string]$target
            Message      = $message
        })
    }

    $healthyCount = @($resultList | Where-Object OverallStatus -eq 'Healthy').Count
    $warningCount = @($resultList | Where-Object OverallStatus -eq 'Warning').Count
    $criticalCount = @($resultList | Where-Object OverallStatus -eq 'Critical').Count
    $incompleteCount = @($resultList | Where-Object OverallStatus -eq 'Incomplete').Count
    $failedCount = $failureList.Count
    $exitCode = if ($failedCount -gt 0 -or $incompleteCount -gt 0) {
        3
    }
    elseif ($criticalCount -gt 0) {
        2
    }
    elseif ($warningCount -gt 0) {
        1
    }
    else {
        0
    }
    $summary = if ($failedCount -gt 0) {
        "$($resultList.Count) of $($targets.Count) computer(s) were checked; $failedCount could not be checked."
    }
    elseif ($criticalCount -gt 0) {
        "All $($targets.Count) computer(s) were checked; $criticalCount need urgent attention."
    }
    elseif ($warningCount -gt 0 -or $incompleteCount -gt 0) {
        "All $($targets.Count) computer(s) were checked; some results need attention or were incomplete."
    }
    else {
        "All $($targets.Count) computer(s) were checked and look good."
    }
    $nextStep = if ($failedCount -gt 0) {
        'Review Failures. EndpointForge will not turn on remote access or install itself on another computer.'
    }
    elseif ($criticalCount -gt 0 -or $warningCount -gt 0) {
        'Review Results, then check an affected computer directly before approving any fixes.'
    }
    elseif ($incompleteCount -gt 0) {
        'Review incomplete results and permissions, then run the check again.'
    }
    else {
        'No action is required.'
    }

    [pscustomobject]@{
        PSTypeName       = 'EndpointForge.FleetSummary'
        SchemaVersion    = '1.0'
        ChecklistName    = [string]$resolvedBaseline.Name
        BaselineName     = [string]$resolvedBaseline.Name
        BaselineVersion  = [string]$resolvedBaseline.Version
        StartedAtUtc     = $startedAtUtc
        CompletedAtUtc   = [DateTime]::UtcNow
        TargetCount      = $targets.Count
        SucceededCount   = $resultList.Count
        FailedCount      = $failedCount
        HealthyCount     = $healthyCount
        WarningCount     = $warningCount
        CriticalCount    = $criticalCount
        IncompleteCount  = $incompleteCount
        IsComplete       = $failedCount -eq 0 -and $incompleteCount -eq 0
        ExitCode          = $exitCode
        Summary           = $summary
        NextStep          = $nextStep
        Results           = $resultList.ToArray()
        Failures          = $failureList.ToArray()
    }
}