THE PROBLEM
Patch Tuesday comes and goes every month. But do you actually know which of your servers applied the updates and which didn't? Most teams find out during an audit — or worse, after a breach. This week we build a script that automatically checks patch status across all your servers and emails you a full compliance report every week.
THE SCRIPT
Save the script below to:
C:\Scripts\PatchComplianceReport.ps1powershell
# Windows Patch Compliance Reporter
# Automate & Operate — automateandoperate.com
# Settings — update these
$from = "[email protected]"
$to = "[email protected]"
$smtpServer = "smtp.gmail.com"
$smtpPort = 587
$username = "[email protected]"
$password = ConvertTo-SecureString "yourpassword" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($username, $password)
$logFile = "C:\Logs\PatchCompliance.log"
$reportFile = "C:\Logs\PatchComplianceReport.csv"
# Days threshold — flag servers not patched within this many days
$patchThresholdDays = 30
# List your servers here
$servers = @(
"localhost",
"SERVER01",
"SERVER02"
)
# Create log folder if missing
if (-not (Test-Path "C:\Logs")) {
New-Item -ItemType Directory -Path "C:\Logs"
}
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$report = @()
$alertServers = @()
foreach ($server in $servers) {
try {
$hotfixes = Get-HotFix -ComputerName $server -ErrorAction Stop |
Sort-Object InstalledOn -Descending |
Select-Object -First 1
$lastPatchDate = $hotfixes.InstalledOn
$daysSinceLastPatch = (New-TimeSpan -Start $lastPatchDate -End (Get-Date)).Days
$status = if ($daysSinceLastPatch -gt $patchThresholdDays) { "OVERDUE" } else { "OK" }
$report += [PSCustomObject]@{
Server = $server
LastPatchDate = $lastPatchDate
DaysSinceLastPatch = $daysSinceLastPatch
LastHotfix = $hotfixes.HotFixID
Status = $status
}
if ($status -eq "OVERDUE") {
$alertServers += $server
}
Add-Content -Path $logFile -Value "$timestamp — $server — Last patched: $lastPatchDate ($daysSinceLastPatch days ago) — $status"
} catch {
$report += [PSCustomObject]@{
Server = $server
LastPatchDate = "UNREACHABLE"
DaysSinceLastPatch = "N/A"
LastHotfix = "N/A"
Status = "ERROR"
}
Add-Content -Path $logFile -Value "$timestamp — $server — ERROR: $_"
}
}
# Export CSV report
$report | Export-Csv -Path $reportFile -NoTypeInformation
# Build email body
$emailBody = "Windows Patch Compliance Report — $timestamp`n"
$emailBody += "Generated on: $env:COMPUTERNAME`n"
$emailBody += "Threshold: Servers not patched in $patchThresholdDays days flagged as OVERDUE`n`n"
$emailBody += "SERVER SUMMARY:`n"
$emailBody += "=" * 60 + "`n"
foreach ($entry in $report) {
$emailBody += "Server: $($entry.Server)`n"
$emailBody += "Last Patch: $($entry.LastPatchDate)`n"
$emailBody += "Days Since Patch: $($entry.DaysSinceLastPatch)`n"
$emailBody += "Status: $($entry.Status)`n"
$emailBody += "-" * 40 + "`n"
}
if ($alertServers.Count -gt 0) {
$emailBody += "`nACTION REQUIRED — Overdue servers:`n"
$alertServers | ForEach-Object { $emailBody += " - $_`n" }
}
$emailBody += "`nFull CSV report saved to: $reportFile`n"
$emailBody += "`nAutomate & Operate — automateandoperate.com"
# Send report
$subject = if ($alertServers.Count -gt 0) {
"PATCH ALERT: $($alertServers.Count) overdue server(s) detected"
} else {
"Patch Compliance Report — All servers up to date"
}
Send-MailMessage `
-From $from -To $to `
-Subject $subject `
-Body $emailBody `
-SmtpServer $smtpServer -Port $smtpPort `
-UseSsl -Credential $credentialUpdate these lines:
$from/$to/$username/$password— your Gmail details$servers— replace with your actual server names$patchThresholdDays— change 30 to however many days you consider overdue
SETTING UP TASK SCHEDULER
Press Windows key → type Task Scheduler → open it
Click "Create Basic Task" → name it
Patch Compliance ReportTrigger: Weekly → Friday → 7:00am
Action: Start a program
Program:
powershell.exeArguments:
-ExecutionPolicy Bypass -File "C:\Scripts\PatchComplianceReport.ps1"Click Next → Finish
Every Friday morning you'll get a full patch compliance report in your inbox before the weekend.
TEST IT
Open PowerShell as administrator and run:
powershell
powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\PatchComplianceReport.ps1"Check C:\Logs\PatchComplianceReport.csv — it will show every server, last patch date, days since patch, and status.
WHY THIS MATTERS
Unpatched servers are the number one attack vector in enterprise breaches. Most IT teams think they're patched — until an auditor or attacker proves otherwise. This script gives you a weekly paper trail showing exactly who is patched and who isn't. That report alone can save you in an audit.
THIS WEEK'S ACTION
Add your 3 most critical servers to the $servers list and run this today. Check the CSV output. If anything shows OVERDUE — you now know before anyone else does.
Reply to this email if you hit any issues — I read every reply.
Automate & Operate — automateandoperate.com
Keep up with tech in 5 minutes
TLDR is the free daily email with summaries of the most interesting stories in startups, tech, and programming. The stuff worth knowing, minus the doomscrolling.
Issues are curated by ex-Google and Anthropic engineers and land in your inbox before your morning coffee. A 5-minute read, and you walk into the day already knowing what your team is still catching up on.
Tech is just the start. We also cover AI, marketing, dev, and more. Pick the briefs that match your work.
Free, daily, and read by 7M+ subscribers. Subscribe and let the experts do the digging for the tech news that matters.
The World's Biggest Dev Event Hits Silicon Valley
500+ speakers. 18 content tracks. Workshops, masterclasses, and the people actually shipping the tools you use every day. WeAreDevelopers World Congress — September 23–25. Use code GITPUSH26 for 10% off.


