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 never installs EndpointForge, turns on remote management, changes a Windows setting, or runs a fix. TcpPort, DnsResolution, HttpEndpointHealth, WindowsUpdateAvailable, and LocalGroupMembership can create observable activity from every remote computer and are blocked unless AllowNetworkChecks is supplied. Each remote computer must already allow PowerShell remoting and must already have EndpointForge 0.6.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 settings and everyday checks a baseline in scripts. In the menu and documentation, that is described simply as a checklist. Matching log lines, event messages, and event data are not included in fleet results. .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 AllowNetworkChecks Allows the five network-active types after every destination, requested account identity, purpose, update-scan option, and source computer has been reviewed. Each remote computer performs its own activity. Contacted services, identity providers, firewalls, proxies, and monitoring tools may record it. This acknowledgement does not grant network authorization. .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. .EXAMPLE Get-EFFleetSummary -ComputerName PC-101,PC-102 -Baseline .\Contoso.EverydayChecks.json -AllowNetworkChecks Explicitly allows the reviewed network-active checks from each remote computer. .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, [switch]$AllowNetworkChecks, [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 $networkControls = @($resolvedBaseline.Controls | Where-Object { Test-EFControlUsesNetwork -Control $_ }) if ($networkControls.Count -gt 0 -and -not $AllowNetworkChecks) { $networkTypes = @($networkControls.Type | Sort-Object -Unique) -join ', ' throw [System.InvalidOperationException]::new( "This checklist contains $($networkControls.Count) network-active check(s): $networkTypes. " + "Across $($targets.Count) computer(s), that activity would originate from every remote computer and may be recorded. " + 'Review the checklist, then add -AllowNetworkChecks to permit them.' ) } $startedAtUtc = [DateTime]::UtcNow $remoteScript = { param($Checklist, $CollectSoftware, $FreeSpaceThreshold, $UptimeThreshold, $PermitNetworkChecks) Import-Module EndpointForge -MinimumVersion 0.6.0 -Force -ErrorAction Stop $parameters = @{ Baseline = $Checklist IncludeSoftware = [bool]$CollectSoftware MinimumFreeSpacePercent = [int]$FreeSpaceThreshold MaximumUptimeDays = [int]$UptimeThreshold NoProgress = $true AllowNetworkChecks = [bool]$PermitNetworkChecks } $checkup = Get-EFEndpointSummary @parameters [pscustomobject]@{ RemoteComputerName = $env:COMPUTERNAME Checkup = $checkup } } $invokeParameters = @{ ComputerName = $targets.ToArray() ScriptBlock = $remoteScript ArgumentList = @($resolvedBaseline, [bool]$IncludeSoftware, $MinimumFreeSpacePercent, $MaximumUptimeDays, [bool]$AllowNetworkChecks) 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.6.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 NetworkCheckCount = $networkControls.Count NetworkChecksAllowed = [bool]$AllowNetworkChecks 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() } } |