Write an honest daily operations report
Export useful details and totals without counting unavailable systems as successfully checked.
Coverage belongs beside the findings
A report with zero findings can mean the checked systems met the rules—or that nothing was checked. Keep requested, checked, and failed counts separate. The supplied result objects use real JSON booleans for Success and include a diagnostic when a snapshot was unavailable.
Export one row per requested system to hosts.csv. Successful rows have Status Checked and their finding count. Failed rows have Status Unavailable, an empty FindingCount field, and their diagnostic. Zero would misleadingly suggest a completed assessment with no findings.
Make the summary reusable
Your function accepts Results, OutputDirectory, and RunId. Sum findings only from successful results. Coverage is Empty for zero requested systems, Incomplete if any result failed, and Complete otherwise. Complete describes collection coverage, not a claim that every system is healthy.
Get-Content '/home/user/results.json' -Raw | ConvertFrom-Json |
Select-Object Computer, Success, FindingCount
Keep FailedHosts a sorted unique array, including zero or one host. Preserve the CSV header even for an empty run so downstream consumers can still recognise the schema. Re-import the saved files to validate the actual exported result.
In the capstone, the findings will include disk capacity, service-state differences, and backup observations. A successful backup job still does not prove restoreability; a report should state the limited guarantee its data supports. Use these outputs to direct review and follow-up, not to invent certainty where observations are missing.
Get-Content '/home/user/results.json' -Raw | ConvertFrom-Json | Group-Object Success | Select-Object Name, Count
Put it into practice.
Define Write-OperationsReport -Results -OutputDirectory -RunId. Write hosts.csv (Computer, Status, FindingCount, Error) and summary.json (RunId, RequestedCount, CheckedCount, FailedCount, FindingCount, Coverage, FailedHosts). Success true rows are Checked; failed rows are Unavailable with an empty finding count. Sum findings only from successes. Coverage is Empty/Incomplete/Complete as described. Preserve headers and array shapes for empty input. Return the summary object.
See an explained solution
function Write-OperationsReport {
param([object[]]$Results, [string]$OutputDirectory, [string]$RunId)
$all = @($Results)
$checked = @($all | Where-Object Success -EQ $true)
$failed = @($all | Where-Object Success -EQ $false)
$rows = @($all | ForEach-Object {
[pscustomobject]@{ Computer=$_.Computer; Status=$(if ($_.Success) { 'Checked' } else { 'Unavailable' }); FindingCount=$(if ($_.Success) { $_.FindingCount } else { $null }); Error=$_.Error }
})
New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null
$csvPath = Join-Path $OutputDirectory 'hosts.csv'
if ($rows.Count) { $rows | Export-Csv -LiteralPath $csvPath -NoTypeInformation }
else { 'Computer,Status,FindingCount,Error' | Set-Content -LiteralPath $csvPath }
$count = 0
foreach ($row in $checked) { $count += $row.FindingCount }
$summary = [pscustomobject]@{ RunId=$RunId; RequestedCount=$all.Count; CheckedCount=$checked.Count; FailedCount=$failed.Count; FindingCount=$count; Coverage=$(if (-not $all.Count) { 'Empty' } elseif ($failed.Count) { 'Incomplete' } else { 'Complete' }); FailedHosts=@($failed.Computer | Sort-Object -Unique) }
$summary | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json')
$summary
}
$results = @(Get-Content '/home/user/results.json' -Raw | ConvertFrom-Json)
Write-OperationsReport -Results $results -OutputDirectory '/home/user/daily-report' -RunId 'OPS-031'
The report separates coverage from findings. Unavailable systems retain their diagnostic and an empty count, while a valid empty run has a schema and an explicit Empty status instead of an unsupported all-clear.