Private/Write-WinClatFailureSummary.ps1

Function Write-WinClatFailureSummary {

    <#

    .SYNOPSIS
        Writes one warning at the end of a fleet run when any computer did not complete.

    .DESCRIPTION
        Failures are not written as errors per computer; each result's Status and Message carry the outcome (DESIGN.md, section 4.6). This one-line warning keeps failures from being missed in a long table.

        When every failed computer has the same status, the warning names it:

            WARNING: 4 of 37 computers were unreachable. Use Format-List to see the reason in each result's Message property.

        Failed, which says nothing more than that, uses the action instead: '3 of 37 computers could not be changed.' When the statuses differ, they are listed in the order unreachable, feature not available, elevation required, failed:

            WARNING: 5 of 37 computers could not be changed: 3 unreachable, 1 elevation required, 1 failed. Use Format-List to see the reason in each result's Message property.

        Nothing is written when every computer completed, or when the run was for a single computer, whose result is already on screen with its Status and Message.

    .PARAMETER Tally
        The tally from New-WinClatTally, filled by Measure-WinClatResult.

    .PARAMETER Action
        What the command does to each computer: queried (Get-, Test-, and Export- commands) or changed (Set- and Import- commands). The default is queried.

    .PARAMETER Cmdlet
        The $PSCmdlet of the public function, so the warning is attributed to it and honors its -WarningAction.

    .INPUTS
        None. This function does not accept pipeline input.

    .OUTPUTS
        None. The warning is written to the warning stream.

    .EXAMPLE
        Write-WinClatFailureSummary -Tally $Tally -Action 'changed' -Cmdlet $PSCmdlet

        Writes 'WARNING: 1 of 10 computers was unreachable. ...' when one computer of ten was unreachable.

    .LINK
        https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-warning

    .NOTES
        Version: 1.0.0
        Creation Date: October 28, 2026
        Last Updated: October 28, 2026
        Author: Richard Hicks
        Organization: Richard M. Hicks Consulting, Inc.
        Contact: rich@richardhicks.com
        Website: https://www.richardhicks.com/

    #>


    [CmdletBinding()]
    [OutputType([void])]

    Param (

        [Parameter(Mandatory)]
        [hashtable]$Tally,

        [ValidateSet('queried', 'changed')]
        [string]$Action = 'queried',

        [System.Management.Automation.PSCmdlet]$Cmdlet

    )

    If (-not $Cmdlet) {

        $Cmdlet = $PSCmdlet

    }

    # A single computer's result is already on screen with its Status and Message.
    If ($Tally.Failed.Count -eq 0 -or $Tally.Single) {

        Return

    }

    # The list form, and the predicate for one computer and for several, of each failure status. Failed says nothing
    # more than that, so it uses the action.
    $Phrases = @{

        Unreachable         = @{ List = 'unreachable'; One = 'was unreachable'; Many = 'were unreachable' }
        FeatureNotAvailable = @{ List = 'feature not available'; One = 'does not have the WinCLAT feature'; Many = 'do not have the WinCLAT feature' }
        ElevationRequired   = @{ List = 'elevation required'; One = 'requires elevation'; Many = 'require elevation' }
        Failed              = @{ List = 'failed'; One = "could not be $Action"; Many = "could not be $Action" }

    }

    $Counts = [ordered]@{}

    ForEach ($Status in $Tally.FailureStatus) {

        $Count = @($Tally.Failed.Values | Where-Object -FilterScript { $_ -eq $Status }).Count

        If ($Count -gt 0) {

            $Counts[$Status] = $Count

        }

    }

    $Failed = $Tally.Failed.Count
    $Noun = 'computers'

    If ($Tally.Computers.Count -eq 1) {

        $Noun = 'computer'

    }

    If ($Counts.Count -eq 1) {

        $Status = @($Counts.Keys)[0]
        $Sentence = "$Failed of $($Tally.Computers.Count) $Noun $($Phrases[$Status].Many)."

        If ($Failed -eq 1) {

            $Sentence = "$Failed of $($Tally.Computers.Count) $Noun $($Phrases[$Status].One)."

        }

    }

    Else {

        $Sentence = "$Failed of $($Tally.Computers.Count) $Noun could not be ${Action}: $(@($Counts.Keys | ForEach-Object -Process { "$($Counts[$_]) $($Phrases[$_].List)" }) -join ', ')."

    }

    $Cmdlet.WriteWarning("$Sentence Use Format-List to see the reason in each result's Message property.")

}