<#
.SYNOPSIS
Collects drive space details from a list of Windows servers and emails an HTML report.
FIX NOTES (applied to match the SQL health-check report):
1. Container width now matches the other report: width="1000" with
style="width:100%;max-width:1000px;" so it is fluid-responsive instead
of a hard-coded 1100px block that can overflow narrow Outlook Web panes.
2. Rounded corners + border-collapse:collapse do not render together in
Outlook Web (the browser drops the radius the moment collapse is on).
The results table now uses the same "wrapper" trick as the SQL report:
an outer table with border-radius + overflow:hidden clips the corners,
and the inner table uses border-collapse:separate with per-cell
bottom/right borders instead of collapsed borders.
3. Header <th> cells get bgcolor + explicit inline styles (not just a
background on the parent <tr>), and the first/last header cells carry
their own top-corner radius as a fallback for clients that strip
overflow:hidden.
4. Server / DeviceID values are HTML-encoded before being written into the
table, so a stray "&" or "<" in a name can't break the layout.
#>
# ---- CONFIG ----
$serverListPath = "C:\Scripts\servers.txt"
$servers = Get-Content $serverListPath |
Where-Object { $_.Trim() -ne "" }
$smtpServer = "smtp.yourcompany.com"
$from = "monitoring@yourcompany.com"
$to = "you@yourcompany.com"
$subject = "Windows Servers - Drive Space Report - $(Get-Date -Format 'yyyy-MM-dd')"
$freeThreshold = 20 # percent free below which status = Check Now
# ---- HELPER: HTML-encode dynamic text ----
function ConvertTo-HtmlSafe {
param([string]$Text)
if ([string]::IsNullOrEmpty($Text)) { return $Text }
return $Text.Replace('&','&').Replace('<','<').Replace('>','>')
}
# ---- COLLECT DATA ----
$results = foreach ($server in $servers) {
try {
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $server -Filter "DriveType=3" -ErrorAction Stop
foreach ($disk in $disks) {
$sizeGB = [math]::Round($disk.Size / 1GB, 2)
$freeGB = [math]::Round($disk.FreeSpace / 1GB, 2)
$usedGB = [math]::Round($sizeGB - $freeGB, 2)
$pctFree = if ($sizeGB -gt 0) { [math]::Round(($freeGB / $sizeGB) * 100, 1) } else { 0 }
$status = if ($pctFree -lt $freeThreshold) { "Check Now" } else { "Healthy" }
[PSCustomObject]@{
Server = $server
DeviceID = $disk.DeviceID
SizeGB = $sizeGB
UsedGB = $usedGB
FreeGB = $freeGB
PercentFree = $pctFree
Status = $status
}
}
}
catch {
[PSCustomObject]@{
Server = $server
DeviceID = "-"
SizeGB = $null
UsedGB = $null
FreeGB = $null
PercentFree = $null
Status = "Server not reachable"
}
}
}
# ---- STYLE FRAGMENTS (reused per cell, Outlook-safe inline styles) ----
$font = "font-family:'Segoe UI',Arial,sans-serif;"
$tdStyle = "padding:8px 12px;font-size:13px;color:#333333;border-bottom:1px solid #e0e0e0;border-right:1px solid #e0e0e0;$font"
$thStyle = "padding:10px 12px;text-align:left;font-size:12px;font-weight:600;color:#ffffff;border-right:1px solid #274fa3;$font"
# ---- BUILD HTML OUTPUT WITH INLINE STYLES ----
$rows = foreach ($r in $results) {
$statusStyle = switch ($r.Status) {
"Healthy" { "color:#2e7d32;font-weight:bold;" }
"Check Now" { "color:#c62828;font-weight:bold;" }
"Server not reachable" { "color:#c62828;font-weight:bold;background-color:#ffebee;" }
default { "" }
}
$serverSafe = ConvertTo-HtmlSafe $r.Server
$deviceIdSafe = ConvertTo-HtmlSafe $r.DeviceID
"<tr>
<td style='$tdStyle'>$serverSafe</td>
<td style='$tdStyle'>$deviceIdSafe</td>
<td style='$tdStyle'>$($r.SizeGB)</td>
<td style='$tdStyle'>$($r.UsedGB)</td>
<td style='$tdStyle'>$($r.FreeGB)</td>
<td style='$tdStyle'>$($r.PercentFree)</td>
<td style='padding:8px 12px;font-size:13px;border-bottom:1px solid #e0e0e0;border-right:1px solid #e0e0e0;$font $statusStyle'>$($r.Status)</td>
</tr>"
}
$html = @"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body style="margin:0;padding:20px;background-color:#f4f6f8;$font">
<!-- Main Container Table (matches SQL health-check report: fluid, max 1000px) -->
<table align="center" width="1000" border="0" cellpadding="0" cellspacing="0" style="width:100%;max-width:1000px;margin:0 auto;">
<!-- HEADER SECTION (BLUE BACKGROUND) -->
<tr>
<td bgcolor="#1e40af" style="background-color:#1e40af;border-radius:8px;padding:18px 20px;text-align:center;">
<h2 style="color:#ffffff;margin:0;font-size:18px;font-weight:600;$font">
Windows Server Drive Space Report - $(Get-Date -Format 'yyyy-MM-dd')
</h2>
</td>
</tr>
<!-- SPACER -->
<tr><td height="16" style="height:16px;font-size:1px;line-height:1px;"> </td></tr>
<!-- RESULTS TABLE SECTION -->
<tr>
<td align="center">
<!-- Rounded-corner wrapper: outer table clips (radius + overflow:hidden), inner table uses
separate borders. This combination is required for Outlook Web to render the radius -
border-collapse:collapse and border-radius do not work together in OWA. -->
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="width:100%;border-collapse:separate;border-spacing:0;background-color:#ffffff;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,0.1);">
<tr><td style="padding:0;">
<!-- Horizontal-scroll wrapper: when the reading pane / window is narrower than the
table's minimum readable width, Outlook Web and mobile scroll this row sideways
instead of squeezing every column illegible. Classic desktop Outlook ignores
overflow-x and just lets the table run past the edge - same as before, but with
a defined floor width instead of wrapped, cramped text. -->
<div style="width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="width:100%;min-width:650px;border-collapse:separate;border-spacing:0;margin:0 -1px -1px 0;">
<thead>
<tr>
<th bgcolor="#1e3a8a" style="background-color:#1e3a8a;$thStyle border-top-left-radius:7px;">Server</th>
<th bgcolor="#1e3a8a" style="background-color:#1e3a8a;$thStyle">DeviceID</th>
<th bgcolor="#1e3a8a" style="background-color:#1e3a8a;$thStyle">Size (GB)</th>
<th bgcolor="#1e3a8a" style="background-color:#1e3a8a;$thStyle">Used (GB)</th>
<th bgcolor="#1e3a8a" style="background-color:#1e3a8a;$thStyle">Free (GB)</th>
<th bgcolor="#1e3a8a" style="background-color:#1e3a8a;$thStyle">% Free</th>
<th bgcolor="#1e3a8a" style="background-color:#1e3a8a;padding:10px 12px;text-align:left;font-size:12px;font-weight:600;color:#ffffff;$font border-top-right-radius:7px;">Status</th>
</tr>
</thead>
<tbody>
$($rows -join "`n")
</tbody>
</table>
</div>
</td></tr>
</table>
</td>
</tr>
<!-- SPACER -->
<tr><td height="16" style="height:16px;font-size:1px;line-height:1px;"> </td></tr>
<!-- SEPARATE AUTOMATED DISCLAIMER BOX -->
<tr>
<td bgcolor="#eff6ff" style="background-color:#eff6ff;border:1px solid #bfdbfe;border-left:4px solid #1e40af;border-radius:6px;padding:10px 14px;text-align:center;">
<span style="font-size:12px;color:#1e40af;font-weight:600;$font">
Note: This is an automated report do not reply to this email.
</span>
</td>
</tr>
<!-- SPACER -->
<tr><td height="16" style="height:16px;font-size:1px;line-height:1px;"> </td></tr>
<!-- SIGNATURE SECTION (BLUE BACKGROUND) -->
<tr>
<td bgcolor="#1e40af" style="background-color:#1e40af;border-radius:8px;padding:14px 20px;text-align:left;">
<p style="color:#ffffff;margin:0;font-size:13px;line-height:1.5;$font">
Regards,<br>
<strong style="color:#ffffff;font-size:14px;">SQL DBA Team</strong><br>
<span style="color:#dbeafe;">Database Administration</span>
</p>
</td>
</tr>
</table>
</body>
</html>
"@
# ---- SEND EMAIL ----
Send-MailMessage -From $from -To $to -Subject $subject -Body $html -BodyAsHtml -SmtpServer $smtpServer
Post a Comment