Automation · 2 min read
Automate Windows Server Health Checks with PowerShell and Email Alerts
Checking server health by logging in and eyeballing Task Manager doesn't scale past one or two machines. This script checks disk space, memory, and a list of critical services, and only sends an email when something's actually wrong — not a daily "everything's fine" notification you'll learn to ignore within a week.
Before you start
You'll need PowerShell 5.1+ (built into Windows Server) and SMTP credentials for sending mail — an app password if you're using Microsoft 365 or Gmail, or your internal relay if you have one.
Step 1: Write the check
Each check appends to an $issues array only when something crosses a threshold. Nothing gets added when things are fine, which is what keeps the email quiet on a normal day.
# health-check.ps1
$diskThreshold = 15 # percent free
$memThreshold = 10 # percent free
$services = @("W3SVC", "MSSQLSERVER", "Spooler")
$issues = @()
Get-PSDrive -PSProvider FileSystem | ForEach-Object {
$freePct = ($_.Free / ($_.Free + $_.Used)) * 100
if ($freePct -lt $diskThreshold) {
$issues += "Drive $($_.Name): $([math]::Round($freePct,1))% free"
}
}
$os = Get-CimInstance Win32_OperatingSystem
$memPct = ($os.FreePhysicalMemory / $os.TotalVisibleMemorySize) * 100
if ($memPct -lt $memThreshold) {
$issues += "Memory: $([math]::Round($memPct,1))% free"
}
foreach ($svc in $services) {
$status = Get-Service -Name $svc -ErrorAction SilentlyContinue
if (-not $status -or $status.Status -ne "Running") {
$issues += "Service $svc is not running"
}
}Step 2: Only email when there's something to say
if ($issues.Count -gt 0) {
Send-MailMessage -To "you@solutionscraft.com" -From "server-alerts@yourdomain.com" `
-Subject "Server health check: $($issues.Count) issue(s)" `
-Body ($issues -join "`n") `
-SmtpServer "smtp.yourprovider.com" -Port 587 -UseSsl `
-Credential (Get-StoredCredential -Target "smtp-alerts")
}Tip: Don't hardcode the SMTP password in the script.
Get-StoredCredential(from theCredentialManagermodule) pulls it from Windows Credential Manager instead, so the script stays safe to commit or share.
Step 3: Run it on a schedule
Register it as a scheduled task so it runs without you:
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-File C:\Scripts\health-check.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At 8am
Register-ScheduledTask -TaskName "ServerHealthCheck" -Action $action -Trigger $triggerWhat's next
If you're monitoring more than a couple of servers, this is also a good candidate for an n8n Execute Command node instead of Task Scheduler — you get a run history and retry logic for free, the same tradeoff covered in the n8n workflow tutorial.