PowerShell HTML Report: Build Styled Reports with CSS

A plain-text PowerShell output pasted into an email tells the story but loses the audience. A styled HTML report with color-coded status rows, bold headers, and a clean table layout communicates the same data in a format executives and operations staff can scan in seconds. PowerShell generate HTML report using ConvertTo-Html with custom CSS gives you polished, professional output without any extra modules. This post covers basic conversion, CSS styling, conditional row colors, multi-table reports, and emailing the result.
ConvertTo-Html Basic Usage
ConvertTo-Html converts PowerShell objects to an HTML table string. Pass it the properties you want as columns and it handles the markup:
$data = Get-Service | Where-Object StartType -eq Automatic |
Select-Object Name, DisplayName, Status
$html = $data | ConvertTo-Html -Title "Service Status Report" -PreContent "<h2>Automatic Services</h2>"
$html | Out-File "C:\Reports\services.html" -Encoding UTF8
Write-Host "Report saved"
The output is a complete HTML document with <html>, <head>, and <body> tags. The -PreContent and -PostContent parameters inject HTML before and after the table.
Add CSS Styling with -Head
Inject a CSS style block via the -Head parameter to transform the default browser-style table into a polished report:
$css = @"
<style>
body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 13px; margin: 20px; }
h2 { color: #0078d4; border-bottom: 2px solid #0078d4; padding-bottom: 4px; }
table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
th { background-color: #0078d4; color: white; padding: 8px 12px; text-align: left; }
td { padding: 6px 12px; border-bottom: 1px solid #e0e0e0; }
tr:nth-child(even) td { background-color: #f5f5f5; }
</style>
"@
$html = $data | ConvertTo-Html -Head $css -Title "Service Report" -PreContent "<h2>Automatic Services</h2>"
$html | Out-File "C:\Reports\services-styled.html" -Encoding UTF8
Conditional Row Color with PostContent
PowerShell’s ConvertTo-Html does not natively support per-row CSS classes. The practical workaround is to post-process the HTML string and inject class attributes using string replacement:
$css = @"
<style>
body { font-family: Segoe UI, sans-serif; font-size: 13px; margin: 20px; }
th { background:#0078d4; color:white; padding:8px 12px; }
td { padding:6px 12px; border-bottom:1px solid #ddd; }
.stopped { background-color: #ffe0e0; }
.running { background-color: #e0ffe0; }
</style>
"@
$html = Get-Service | Where-Object StartType -eq Automatic |
Select-Object Name, DisplayName, Status |
ConvertTo-Html -Head $css -PreContent "<h2>Service Status</h2>"
# Inject CSS classes by replacing table cell content patterns
$html = $html -replace '<td>Stopped</td>', '<td class="stopped">Stopped</td>'
$html = $html -replace '<td>Running</td>', '<td class="running">Running</td>'
$html | Out-File "C:\Reports\services-color.html" -Encoding UTF8
Multiple Tables in One Report
Combine multiple data sets into a single HTML report using -PreContent to separate sections and string concatenation:
$svcHtml = Get-Service | Where-Object Status -eq Stopped |
Select-Object Name, Status |
ConvertTo-Html -Fragment -PreContent "<h2>Stopped Services</h2>"
$diskHtml = Get-PSDrive -PSProvider FileSystem |
Select-Object Name, @{N='FreeGB';E={[Math]::Round($_.Free/1GB,1)}},
@{N='UsedGB';E={[Math]::Round($_.Used/1GB,1)}} |
ConvertTo-Html -Fragment -PreContent "<h2>Disk Space</h2>"
$report = ConvertTo-Html -Head $css -Title "Server Report" `
-PreContent "<h1>$env:COMPUTERNAME — $(Get-Date -Format 'yyyy-MM-dd')</h1>" `
-Body ($svcHtml + $diskHtml)
$report | Out-File "C:\Reports\combined-report.html" -Encoding UTF8
Embed Charts with Pre-Built HTML
For simple bar charts without JavaScript libraries, embed a CSS-only chart using absolute-width div elements scaled to percentages:
$diskBars = Get-PSDrive -PSProvider FileSystem | ForEach-Object {
$usedPct = if ($_.Used + $_.Free -gt 0) {
[int](($_.Used / ($_.Used + $_.Free)) * 100)
} else { 0 }
"<p><strong>$($_.Name):</strong> <span style='display:inline-block;width:${usedPct}%;background:#0078d4;color:white;padding:2px 4px'>${usedPct}%</span></p>"
}
$chartSection = "<h2>Disk Usage</h2>" + ($diskBars -join '')
Save and Email the Report
Save the HTML file and send it as an HTML email body or attachment:
$reportPath = "C:\Reports\server-report-$(Get-Date -Format 'yyyyMMdd').html"
$report | Out-File $reportPath -Encoding UTF8
# Send as HTML email body
$emailBody = $report | Out-String
Send-MailMessage -From "[email protected]" -To "[email protected]" `
-Subject "Server Report $(Get-Date -Format 'yyyy-MM-dd')" `
-Body $emailBody -BodyAsHtml `
-SmtpServer "smtp-relay.corp.com"
Write-Host "Report emailed"
Common Errors and Fixes
-
ConvertTo-Html outputs string — must set encoding on Out-File. The default
Out-Fileencoding on Windows PowerShell 5.1 is UTF-16 LE, which browsers may not render correctly for reports containing special characters. Always use-Encoding UTF8withOut-Filefor HTML reports. -
CSS in -Head must be inside <style> tags. Passing raw CSS without the
<style>...</style>wrapper to-Headresults in the CSS text appearing as visible body content in the browser. Always wrap your CSS in a proper<style>element.
Related Cmdlets / See Also
Wrapping Up
ConvertTo-Html with a CSS -Head block produces professional HTML reports from any PowerShell data. Use -Fragment for individual table sections, combine them with string concatenation, post-process with string replacement for row-level colors, and always write with -Encoding UTF8. The result is a report you can be proud to email to stakeholders.


