PowerShell HTML operations report starter

Concrete PowerShell reporting pattern for turning host-check results into an HTML operations summary with a status rollup, per-host table, failure section, saved local artifacts, and optional email delivery.

Good For

  • Scheduled health summaries
  • Post-maintenance evidence emails
  • Remote host list reporting
  • Ticket attachments and change records
  • Standardizing script output across teams

How to Use It

  1. Start with a fixed result schema, not ad hoc strings. For each checked host or target, capture a small object with fields such as Host, Status, Finding, ErrorText, DurationMs, and CheckGroup. Use the same property names every time so the HTML table, CSV export, and any future JSON output all stay aligned.
  2. Build a short run summary before rendering the page. Count Passed, Warning, Failed, and Unreachable targets, then stamp the report with RunId, UTC timestamp, script name, environment, and input scope. The point is to let someone skim the outcome in 10 seconds before they drill into host rows.
  3. Render the HTML in two layers: a summary block and a per-host table. Put the overall counts and run metadata at the top, then a compact table for each host result. If a host failed, surface the failure text in its own row or failure section so the report does not bury the actual problem under generic green checks.
  4. Always write local evidence first. Save the HTML report, the raw CSV export, and a plain text execution log before attempting email delivery. That way the report still has value if the mail relay, Graph send path, or notification workflow fails during the last step.
  5. Treat email as optional delivery, not the only output. Use the saved HTML as the system of record, then send it as the message body or as an attachment. Keep recipients, subject pattern, and delivery result in the log so the report doubles as ticket evidence instead of a throwaway notification.
  6. Add a preview and undo rule for sample or failed runs. Write reports into a dedicated output folder, then delete or archive the generated HTML, CSV, and log files if the run used fake data, wrong scope, or sensitive test output that should not remain on disk.

Execution Modes

  • local

Inputs and Outputs

Inputs

  • Per-host result objects from your script
  • Run metadata such as environment, scope, and script name
  • Optional mail settings
  • Optional credential or token retrieval path

Outputs

  • html-report
  • csv
  • operator-notes

Command Starter

Changes system state: review before running

# ---------------------------------------------------------------------
# Minimal local reporting scaffold
# ---------------------------------------------------------------------
$RunId = Get-Date -Format 'yyyyMMdd-HHmmss'
$OutputRoot = '.\output\html-ops-report'
New-Item -ItemType Directory -Path $OutputRoot -Force | Out-Null

# Result rows should already be normalized by the collecting script.
$Results = @(
    [pscustomobject]@{ ComputerName='server01'; Status='Pass'; Finding='Disk and service checks clean.' },
    [pscustomobject]@{ ComputerName='server02'; Status='Warning'; Finding='Pending reboot detected.' }
)

$Summary = [pscustomobject]@{
    RunId = $RunId
    TotalRows = $Results.Count
    PassRows = @($Results | Where-Object Status -eq 'Pass').Count
    WarningRows = @($Results | Where-Object Status -eq 'Warning').Count
}

$CsvPath = Join-Path $OutputRoot "$RunId-results.csv"
$HtmlPath = Join-Path $OutputRoot "$RunId-summary.html"

$Results | Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8

$HtmlFragments = @()
$HtmlFragments += '<h1>Operations Report</h1>'
$HtmlFragments += ($Summary | ConvertTo-Html -Fragment)
$HtmlFragments += '<h2>Findings</h2>'
$HtmlFragments += ($Results | ConvertTo-Html -Fragment)
$HtmlDocument = ConvertTo-Html -Title 'Operations Report' -Body ($HtmlFragments -join [Environment]::NewLine)
$HtmlDocument | Set-Content -Path $HtmlPath -Encoding UTF8

# Delivery should happen after durable artifacts are written.
# Do not use deprecated Send-MailMessage as the default production path.
[pscustomobject]@{ CsvPath=$CsvPath; HtmlPath=$HtmlPath }

Validation

  • A known sample run renders the expected pass and fail counts in the HTML summary and shows both hosts in the output table.
  • The saved CSV contains the same hosts and status values as the HTML report, without extra rows or renamed columns.
  • If email delivery is enabled, the delivered message keeps the summary counts and failure text readable instead of truncating them.
  • If delivery fails, the local HTML, CSV, and execution log still exist and the failure is recorded in the log.

Reporting

  • Use this as the smallest reusable scaffold for a local HTML plus CSV report.
  • Render local artifacts first, then attach or deliver them through an approved mail/ticketing path.
  • For recurring report families, move to the dedicated reporting foundation pages so HTML, CSV, JSON, and logs share one contract.

Safety Notes

  • Do not embed SMTP passwords, API tokens, or service account credentials in source code, scheduled task arguments, or transcript logs.
  • Avoid including secrets, full connection strings, or sensitive personal data in HTML output, CSV exports, or email bodies.
  • Treat email as a delivery channel, not the system of record; always save local evidence artifacts before send attempts.
  • If reporting on remote hosts, record unreachable targets as evidence instead of retrying aggressively in a way that could amplify outages.
  • Generated HTML, CSV, and log files are local state changes. Treat a sample run as a preview pass, keep outputs in a dedicated report folder, and use delete or archive as the undo path when test artifacts should not be retained.