Build a PowerShell System Monitoring Dashboard in 30 Minutes
Your server is running slow. By the time someone notices and calls you, the damage is done: customers are complaining, sales are lost, and you're scrambling to diagnose what went wrong two hours ago.
What if you had a dashboard that showed CPU, memory, disk, and critical services in real-timeβand automatically alerted you before things broke?
I'll show you how to build a professional system monitoring dashboard using only PowerShell. No expensive monitoring tools. No complicated setup. Just a script that runs on any Windows server or workstation and gives you the visibility you need.
What You'll Build
By the end of this tutorial, you'll have:
- Real-time monitoring dashboard showing CPU, RAM, disk, and network usage
- Service status tracking for critical services (SQL Server, IIS, etc.)
- Automatic email alerts when thresholds are exceeded
- HTML reports that can be emailed or viewed in a browser
- Automated scheduling to run every 5 minutes
Time to build: 30-45 minutes Skill level: Intermediate PowerShell (I'll explain everything) Works on: Windows Server 2016+, Windows 10/11
Prerequisites
You'll need:
- Windows machine with PowerShell 5.1 or PowerShell 7+
- Administrator privileges (for some system metrics)
- SMTP email server access (Gmail, Office 365, or company server)
- Basic PowerShell knowledge (variables, loops, functions)
Check your PowerShell version:
1$PSVersionTable.PSVersion
If you're below 5.1, update PowerShell or use Windows Management Framework 5.1.
Architecture Overview
Here's what we're building:
ββββββββββββββββββββββββββββ
β System Metrics Module β β Collect CPU, RAM, Disk data
ββββββββββββββ¬ββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββ
β Service Check Module β β Check critical services
ββββββββββββββ¬ββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββ
β Threshold Checker β β Compare against limits
ββββββββββββββ¬ββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββ
β HTML Report Generator β β Create visual dashboard
ββββββββββββββ¬ββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββ
β Email Alert Sender β β Send alerts if needed
ββββββββββββββββββββββββββββStep 1: Set Up Project Structure
Create a clean folder structure:
1# Create project folder2New-Item -Path "C:\Scripts\ServerMonitor" -ItemType Directory -Force3Set-Location "C:\Scripts\ServerMonitor"45# Create subfolders6New-Item -Path ".\Modules" -ItemType Directory -Force7New-Item -Path ".\Reports" -ItemType Directory -Force8New-Item -Path ".\Logs" -ItemType Directory -Force910# Create main files11New-Item -Path ".\config.ps1" -ItemType File -Force12New-Item -Path ".\monitoring.ps1" -ItemType File -Force13New-Item -Path ".\email-alert.ps1" -ItemType File -Force
Step 2: Create Configuration File
Create config.ps1 with your settings:
1# config.ps1 - Configuration settings23# Email settings4$EmailConfig = @{5 SmtpServer = "smtp.gmail.com"6 Port = 5877 UseSsl = $true8 From = "your-email@gmail.com"9 To = @("admin@company.com", "ops-team@company.com")10 Username = "your-email@gmail.com"11 Password = "your-app-password" # Use App Password for Gmail12}1314# Monitoring thresholds15$Thresholds = @{16 CpuPercent = 80 # Alert if CPU > 80%17 MemoryPercent = 85 # Alert if Memory > 85%18 DiskSpacePercent = 90 # Alert if Disk > 90% full19 DiskSpaceGB = 10 # Alert if < 10 GB free20}2122# Critical services to monitor23$CriticalServices = @(24 "W3SVC", # IIS25 "MSSQLSERVER", # SQL Server26 "WinRM", # Remote Management27 "EventLog", # Event Log service28 "Spooler" # Print Spooler29)3031# Report settings32$ReportPath = "C:\Scripts\ServerMonitor\Reports"33$LogPath = "C:\Scripts\ServerMonitor\Logs"34$RetentionDays = 7 # Keep reports for 7 days3536# Monitoring interval (for scheduled task)37$MonitoringIntervalMinutes = 53839Write-Host "β Configuration loaded" -ForegroundColor Green
Important: For Gmail, create an App Password instead of using your regular password.
Step 3: Build System Metrics Module
Create Modules\Get-SystemMetrics.ps1:
1# Get-SystemMetrics.ps1 - Collect system performance data23function Get-SystemMetrics {4 param (5 [string]$ComputerName = $env:COMPUTERNAME6 )78 Write-Host "Collecting system metrics..." -ForegroundColor Cyan910 $metrics = @{11 ComputerName = $ComputerName12 Timestamp = Get-Date13 CPU = $null14 Memory = $null15 Disks = @()16 Network = $null17 Uptime = $null18 }1920 try {21 # CPU Usage22 $cpuSamples = Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 323 $cpuAvg = ($cpuSamples.CounterSamples | Measure-Object -Property CookedValue -Average).Average24 $metrics.CPU = @{25 UsagePercent = [math]::Round($cpuAvg, 2)26 Status = if ($cpuAvg -gt $Thresholds.CpuPercent) { "Critical" }27 elseif ($cpuAvg -gt ($Thresholds.CpuPercent * 0.8)) { "Warning" }28 else { "OK" }29 }3031 # Memory Usage32 $os = Get-CimInstance Win32_OperatingSystem33 $totalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)34 $freeMemoryGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)35 $usedMemoryGB = $totalMemoryGB - $freeMemoryGB36 $memoryPercent = [math]::Round(($usedMemoryGB / $totalMemoryGB) * 100, 2)3738 $metrics.Memory = @{39 TotalGB = $totalMemoryGB40 UsedGB = $usedMemoryGB41 FreeGB = $freeMemoryGB42 UsagePercent = $memoryPercent43 Status = if ($memoryPercent -gt $Thresholds.MemoryPercent) { "Critical" }44 elseif ($memoryPercent -gt ($Thresholds.MemoryPercent * 0.8)) { "Warning" }45 else { "OK" }46 }4748 # Disk Usage49 $disks = Get-CimInstance Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 }5051 foreach ($disk in $disks) {52 $totalGB = [math]::Round($disk.Size / 1GB, 2)53 $freeGB = [math]::Round($disk.FreeSpace / 1GB, 2)54 $usedGB = $totalGB - $freeGB55 $usedPercent = [math]::Round(($usedGB / $totalGB) * 100, 2)5657 $diskStatus = if ($usedPercent -gt $Thresholds.DiskSpacePercent -or $freeGB -lt $Thresholds.DiskSpaceGB) { "Critical" }58 elseif ($usedPercent -gt ($Thresholds.DiskSpacePercent * 0.8)) { "Warning" }59 else { "OK" }6061 $metrics.Disks += @{62 DriveLetter = $disk.DeviceID63 TotalGB = $totalGB64 UsedGB = $usedGB65 FreeGB = $freeGB66 UsagePercent = $usedPercent67 Status = $diskStatus68 }69 }7071 # System Uptime72 $uptime = (Get-Date) - $os.LastBootUpTime73 $metrics.Uptime = @{74 Days = $uptime.Days75 Hours = $uptime.Hours76 Minutes = $uptime.Minutes77 TotalHours = [math]::Round($uptime.TotalHours, 1)78 }7980 # Network Statistics (optional - can be slow on some systems)81 try {82 $netAdapter = Get-NetAdapter | Where-Object Status -eq "Up" | Select-Object -First 183 if ($netAdapter) {84 $netStats = Get-NetAdapterStatistics -Name $netAdapter.Name85 $metrics.Network = @{86 AdapterName = $netAdapter.Name87 Status = $netAdapter.Status88 BytesSent = [math]::Round($netStats.SentBytes / 1GB, 2)89 BytesReceived = [math]::Round($netStats.ReceivedBytes / 1GB, 2)90 }91 }92 } catch {93 Write-Warning "Could not retrieve network statistics: $_"94 $metrics.Network = @{ Status = "Unknown" }95 }9697 Write-Host "β System metrics collected" -ForegroundColor Green98 return $metrics99100 } catch {101 Write-Error "Failed to collect system metrics: $_"102 return $null103 }104}105106# Export function107Export-ModuleMember -Function Get-SystemMetrics
Step 4: Build Service Monitoring Module
Create Modules\Get-ServiceStatus.ps1:
1# Get-ServiceStatus.ps1 - Monitor critical services23function Get-ServiceStatus {4 param (5 [string[]]$ServiceNames6 )78 Write-Host "Checking service status..." -ForegroundColor Cyan910 $serviceResults = @()1112 foreach ($serviceName in $ServiceNames) {13 try {14 $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue1516 if ($service) {17 $serviceResults += @{18 Name = $service.Name19 DisplayName = $service.DisplayName20 Status = $service.Status.ToString()21 StartType = $service.StartType.ToString()22 Health = if ($service.Status -eq 'Running') { "OK" } else { "Critical" }23 }24 } else {25 $serviceResults += @{26 Name = $serviceName27 DisplayName = "Service Not Found"28 Status = "NotInstalled"29 StartType = "N/A"30 Health = "Warning"31 }32 }33 } catch {34 Write-Warning "Error checking service $serviceName: $_"35 $serviceResults += @{36 Name = $serviceName37 DisplayName = "Error"38 Status = "Unknown"39 StartType = "N/A"40 Health = "Critical"41 }42 }43 }4445 Write-Host "β Service status checked" -ForegroundColor Green46 return $serviceResults47}4849# Export function50Export-ModuleMember -Function Get-ServiceStatus
Step 5: Create HTML Dashboard Generator
Create Modules\New-HtmlReport.ps1:
1# New-HtmlReport.ps1 - Generate HTML dashboard23function New-HtmlReport {4 param (5 [hashtable]$SystemMetrics,6 [array]$ServiceStatus,7 [string]$OutputPath8 )910 Write-Host "Generating HTML report..." -ForegroundColor Cyan1112 # Helper function for status color13 function Get-StatusColor {14 param($Status)15 switch ($Status) {16 "OK" { return "#28a745" } # Green17 "Warning" { return "#ffc107" } # Yellow18 "Critical" { return "#dc3545" } # Red19 default { return "#6c757d" } # Gray20 }21 }2223 # Build HTML24 $html = @"25<!DOCTYPE html>26<html>27<head>28 <title>System Monitoring Dashboard - $($SystemMetrics.ComputerName)</title>29 <style>30 body {31 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;32 margin: 0;33 padding: 20px;34 background: #f4f6f9;35 }36 .header {37 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);38 color: white;39 padding: 30px;40 border-radius: 10px;41 margin-bottom: 30px;42 box-shadow: 0 4px 6px rgba(0,0,0,0.1);43 }44 .header h1 {45 margin: 0;46 font-size: 32px;47 }48 .header .timestamp {49 opacity: 0.9;50 margin-top: 10px;51 }52 .metrics-grid {53 display: grid;54 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));55 gap: 20px;56 margin-bottom: 30px;57 }58 .metric-card {59 background: white;60 border-radius: 10px;61 padding: 25px;62 box-shadow: 0 2px 8px rgba(0,0,0,0.1);63 border-left: 5px solid #667eea;64 }65 .metric-card h3 {66 margin: 0 0 15px 0;67 color: #333;68 font-size: 16px;69 text-transform: uppercase;70 letter-spacing: 1px;71 }72 .metric-value {73 font-size: 42px;74 font-weight: bold;75 margin: 15px 0;76 }77 .metric-detail {78 color: #666;79 font-size: 14px;80 margin: 5px 0;81 }82 .status-badge {83 display: inline-block;84 padding: 5px 12px;85 border-radius: 20px;86 color: white;87 font-size: 12px;88 font-weight: bold;89 text-transform: uppercase;90 }91 .progress-bar {92 height: 20px;93 background: #e9ecef;94 border-radius: 10px;95 overflow: hidden;96 margin: 10px 0;97 }98 .progress-fill {99 height: 100%;100 transition: width 0.3s ease;101 display: flex;102 align-items: center;103 justify-content: center;104 color: white;105 font-size: 12px;106 font-weight: bold;107 }108 .section {109 background: white;110 border-radius: 10px;111 padding: 25px;112 margin-bottom: 20px;113 box-shadow: 0 2px 8px rgba(0,0,0,0.1);114 }115 .section h2 {116 margin: 0 0 20px 0;117 color: #667eea;118 border-bottom: 3px solid #667eea;119 padding-bottom: 10px;120 }121 table {122 width: 100%;123 border-collapse: collapse;124 }125 th {126 background: #f8f9fa;127 padding: 12px;128 text-align: left;129 font-weight: 600;130 color: #333;131 border-bottom: 2px solid #dee2e6;132 }133 td {134 padding: 12px;135 border-bottom: 1px solid #dee2e6;136 }137 tr:hover {138 background: #f8f9fa;139 }140 .footer {141 text-align: center;142 color: #666;143 margin-top: 30px;144 padding-top: 20px;145 border-top: 1px solid #dee2e6;146 font-size: 12px;147 }148 </style>149</head>150<body>151 <div class="header">152 <h1>π₯οΈ System Monitoring Dashboard</h1>153 <div class="timestamp">Server: $($SystemMetrics.ComputerName) | Generated: $($SystemMetrics.Timestamp.ToString('yyyy-MM-dd HH:mm:ss'))</div>154 <div class="timestamp">Uptime: $($SystemMetrics.Uptime.Days) days, $($SystemMetrics.Uptime.Hours) hours, $($SystemMetrics.Uptime.Minutes) minutes</div>155 </div>156157 <div class="metrics-grid">158 <!-- CPU Card -->159 <div class="metric-card">160 <h3>π» CPU Usage</h3>161 <div class="metric-value" style="color: $(Get-StatusColor $SystemMetrics.CPU.Status)">162 $($SystemMetrics.CPU.UsagePercent)%163 </div>164 <div class="progress-bar">165 <div class="progress-fill" style="width: $($SystemMetrics.CPU.UsagePercent)%; background: $(Get-StatusColor $SystemMetrics.CPU.Status)">166 $($SystemMetrics.CPU.UsagePercent)%167 </div>168 </div>169 <span class="status-badge" style="background: $(Get-StatusColor $SystemMetrics.CPU.Status)">$($SystemMetrics.CPU.Status)</span>170 </div>171172 <!-- Memory Card -->173 <div class="metric-card">174 <h3>π§ Memory Usage</h3>175 <div class="metric-value" style="color: $(Get-StatusColor $SystemMetrics.Memory.Status)">176 $($SystemMetrics.Memory.UsagePercent)%177 </div>178 <div class="progress-bar">179 <div class="progress-fill" style="width: $($SystemMetrics.Memory.UsagePercent)%; background: $(Get-StatusColor $SystemMetrics.Memory.Status)">180 $($SystemMetrics.Memory.UsagePercent)%181 </div>182 </div>183 <div class="metric-detail">Used: $($SystemMetrics.Memory.UsedGB) GB / $($SystemMetrics.Memory.TotalGB) GB</div>184 <span class="status-badge" style="background: $(Get-StatusColor $SystemMetrics.Memory.Status)">$($SystemMetrics.Memory.Status)</span>185 </div>186"@187188 # Add disk cards189 foreach ($disk in $SystemMetrics.Disks) {190 $html += @"191192 <div class="metric-card">193 <h3>πΎ Disk $($disk.DriveLetter)</h3>194 <div class="metric-value" style="color: $(Get-StatusColor $disk.Status)">195 $($disk.UsagePercent)%196 </div>197 <div class="progress-bar">198 <div class="progress-fill" style="width: $($disk.UsagePercent)%; background: $(Get-StatusColor $disk.Status)">199 $($disk.UsagePercent)%200 </div>201 </div>202 <div class="metric-detail">Free: $($disk.FreeGB) GB / $($disk.TotalGB) GB</div>203 <span class="status-badge" style="background: $(Get-StatusColor $disk.Status)">$($disk.Status)</span>204 </div>205"@206 }207208 $html += @"209 </div>210211 <div class="section">212 <h2>βοΈ Critical Services Status</h2>213 <table>214 <thead>215 <tr>216 <th>Service Name</th>217 <th>Display Name</th>218 <th>Status</th>219 <th>Start Type</th>220 <th>Health</th>221 </tr>222 </thead>223 <tbody>224"@225226 foreach ($service in $ServiceStatus) {227 $html += @"228 <tr>229 <td>$($service.Name)</td>230 <td>$($service.DisplayName)</td>231 <td>$($service.Status)</td>232 <td>$($service.StartType)</td>233 <td><span class="status-badge" style="background: $(Get-StatusColor $service.Health)">$($service.Health)</span></td>234 </tr>235"@236 }237238 $html += @"239 </tbody>240 </table>241 </div>242243 <div class="footer">244 <p>Automated System Monitoring | Powered by PowerShell</p>245 <p>Threshold Settings: CPU > $($Thresholds.CpuPercent)% | Memory > $($Thresholds.MemoryPercent)% | Disk > $($Thresholds.DiskSpacePercent)%</p>246 </div>247</body>248</html>249"@250251 # Save HTML file252 $html | Out-File -FilePath $OutputPath -Encoding UTF8253 Write-Host "β HTML report generated: $OutputPath" -ForegroundColor Green254255 return $OutputPath256}257258# Export function259Export-ModuleMember -Function New-HtmlReport
Step 6: Create Email Alert System
Create email-alert.ps1:
1# email-alert.ps1 - Send email alerts23function Send-MonitoringAlert {4 param (5 [hashtable]$EmailConfig,6 [string]$HtmlReportPath,7 [hashtable]$SystemMetrics,8 [array]$ServiceStatus9 )1011 Write-Host "Checking if alerts are needed..." -ForegroundColor Cyan1213 # Check if any metrics are in critical state14 $criticalIssues = @()1516 if ($SystemMetrics.CPU.Status -eq "Critical") {17 $criticalIssues += "CPU usage is at $($SystemMetrics.CPU.UsagePercent)%"18 }1920 if ($SystemMetrics.Memory.Status -eq "Critical") {21 $criticalIssues += "Memory usage is at $($SystemMetrics.Memory.UsagePercent)%"22 }2324 foreach ($disk in $SystemMetrics.Disks) {25 if ($disk.Status -eq "Critical") {26 $criticalIssues += "Disk $($disk.DriveLetter) is at $($disk.UsagePercent)% capacity ($($disk.FreeGB) GB free)"27 }28 }2930 foreach ($service in $ServiceStatus) {31 if ($service.Health -eq "Critical") {32 $criticalIssues += "Service '$($service.DisplayName)' is $($service.Status)"33 }34 }3536 if ($criticalIssues.Count -eq 0) {37 Write-Host "β All systems normal - no alerts needed" -ForegroundColor Green38 return39 }4041 # Build alert email42 Write-Host "β οΈ Critical issues detected - sending alert..." -ForegroundColor Yellow4344 $subject = "π¨ System Alert: $($SystemMetrics.ComputerName) - $($criticalIssues.Count) Critical Issues"4546 $body = @"47<html>48<body style="font-family: Arial, sans-serif;">49 <h2 style="color: #dc3545;">System Alert: Critical Issues Detected</h2>50 <p><strong>Server:</strong> $($SystemMetrics.ComputerName)</p>51 <p><strong>Time:</strong> $($SystemMetrics.Timestamp.ToString('yyyy-MM-dd HH:mm:ss'))</p>52 <p><strong>Issue Count:</strong> $($criticalIssues.Count)</p>5354 <h3>Critical Issues:</h3>55 <ul>56"@5758 foreach ($issue in $criticalIssues) {59 $body += " <li style='color: #dc3545;'>$issue</li>`n"60 }6162 $body += @"63 </ul>6465 <p>Please see the attached detailed monitoring report.</p>6667 <p style="color: #666; font-size: 12px; margin-top: 30px;">68 This is an automated alert from the PowerShell System Monitoring Dashboard.69 </p>70</body>71</html>72"@7374 try {75 # Create secure password76 $securePassword = ConvertTo-SecureString $EmailConfig.Password -AsPlainText -Force77 $credential = New-Object System.Management.Automation.PSCredential($EmailConfig.Username, $securePassword)7879 # Send email80 $mailParams = @{81 SmtpServer = $EmailConfig.SmtpServer82 Port = $EmailConfig.Port83 UseSsl = $EmailConfig.UseSsl84 Credential = $credential85 From = $EmailConfig.From86 To = $EmailConfig.To87 Subject = $subject88 Body = $body89 BodyAsHtml = $true90 Attachments = $HtmlReportPath91 }9293 Send-MailMessage @mailParams9495 Write-Host "β Alert email sent successfully" -ForegroundColor Green9697 } catch {98 Write-Error "Failed to send alert email: $_"99 }100}
Step 7: Create Main Monitoring Script
Create monitoring.ps1:
1# monitoring.ps1 - Main monitoring orchestration script23# Load configuration4. ".\config.ps1"56# Import modules7Import-Module ".\Modules\Get-SystemMetrics.ps1" -Force8Import-Module ".\Modules\Get-ServiceStatus.ps1" -Force9Import-Module ".\Modules\New-HtmlReport.ps1" -Force10. ".\email-alert.ps1"1112Write-Host "`n========================================" -ForegroundColor Cyan13Write-Host " SYSTEM MONITORING DASHBOARD" -ForegroundColor Cyan14Write-Host "========================================`n" -ForegroundColor Cyan1516try {17 # Collect system metrics18 $systemMetrics = Get-SystemMetrics1920 if (-not $systemMetrics) {21 throw "Failed to collect system metrics"22 }2324 # Check service status25 $serviceStatus = Get-ServiceStatus -ServiceNames $CriticalServices2627 # Generate HTML report28 $timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss"29 $reportFileName = "SystemMonitoring_$($env:COMPUTERNAME)_$timestamp.html"30 $reportPath = Join-Path $ReportPath $reportFileName3132 $htmlReport = New-HtmlReport -SystemMetrics $systemMetrics -ServiceStatus $serviceStatus -OutputPath $reportPath3334 # Send alerts if needed35 Send-MonitoringAlert -EmailConfig $EmailConfig -HtmlReportPath $htmlReport -SystemMetrics $systemMetrics -ServiceStatus $serviceStatus3637 # Clean up old reports38 Write-Host "`nCleaning up old reports..." -ForegroundColor Cyan39 Get-ChildItem -Path $ReportPath -Filter "*.html" |40 Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$RetentionDays) } |41 Remove-Item -Force42 Write-Host "β Old reports removed" -ForegroundColor Green4344 Write-Host "`n========================================" -ForegroundColor Green45 Write-Host " MONITORING COMPLETE" -ForegroundColor Green46 Write-Host "========================================" -ForegroundColor Green47 Write-Host "Report: $reportPath`n" -ForegroundColor White4849} catch {50 Write-Error "Monitoring failed: $_"5152 # Log error53 $errorLog = Join-Path $LogPath "error_$(Get-Date -Format 'yyyy-MM-dd').log"54 "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - ERROR: $_" | Out-File -FilePath $errorLog -Append55}
Step 8: Test Your Dashboard
Run the monitoring script:
1cd C:\Scripts\ServerMonitor2.\monitoring.ps1
You should see output like:
======================================== SYSTEM MONITORING DASHBOARD ======================================== Collecting system metrics... β System metrics collected Checking service status... β Service status checked Generating HTML report... β HTML report generated: C:\Scripts\ServerMonitor\Reports\SystemMonitoring_SERVER01_2026-01-16_143052.html Checking if alerts are needed... β All systems normal - no alerts needed Cleaning up old reports... β Old reports removed ======================================== MONITORING COMPLETE ======================================== Report: C:\Scripts\ServerMonitor\Reports\SystemMonitoring_SERVER01_2026-01-16_143052.html
Open the HTML report in your browser to see the beautiful dashboard!
Step 9: Automate with Scheduled Task
Create a scheduled task to run every 5 minutes:
1# Create scheduled task2$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -File C:\Scripts\ServerMonitor\monitoring.ps1"34$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration ([TimeSpan]::MaxValue)56$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest78$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable910Register-ScheduledTask -TaskName "SystemMonitoring" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Automated system monitoring dashboard"1112Write-Host "β Scheduled task created: Runs every 5 minutes" -ForegroundColor Green
Verify the task:
1Get-ScheduledTask -TaskName "SystemMonitoring"
Advanced Enhancements
Enhancement 1: Monitor Remote Servers
Modify Get-SystemMetrics to support remote computers:
1function Get-SystemMetrics {2 param (3 [string]$ComputerName = $env:COMPUTERNAME,4 [PSCredential]$Credential5 )67 $sessionParams = @{8 ComputerName = $ComputerName9 }1011 if ($Credential) {12 $sessionParams.Credential = $Credential13 }1415 Invoke-Command @sessionParams -ScriptBlock {16 # All the metrics collection code here...17 }18}
Then monitor multiple servers:
1$servers = @("SERVER01", "SERVER02", "SERVER03")23foreach ($server in $servers) {4 $metrics = Get-SystemMetrics -ComputerName $server5 # Generate report for each server...6}
Enhancement 2: Add Database Logging
Store metrics in a SQL database for historical analysis:
1function Save-MetricsToDatabase {2 param (3 [hashtable]$SystemMetrics4 )56 $connectionString = "Server=SQL01;Database=Monitoring;Integrated Security=True"7 $connection = New-Object System.Data.SqlClient.SqlConnection($connectionString)8 $connection.Open()910 $query = @"11INSERT INTO SystemMetrics (Timestamp, ComputerName, CpuPercent, MemoryPercent, DiskSpaceGB)12VALUES (@Timestamp, @ComputerName, @CpuPercent, @MemoryPercent, @DiskSpaceGB)13"@1415 $command = $connection.CreateCommand()16 $command.CommandText = $query17 $command.Parameters.AddWithValue("@Timestamp", $SystemMetrics.Timestamp)18 $command.Parameters.AddWithValue("@ComputerName", $SystemMetrics.ComputerName)19 $command.Parameters.AddWithValue("@CpuPercent", $SystemMetrics.CPU.UsagePercent)20 $command.Parameters.AddWithValue("@MemoryPercent", $SystemMetrics.Memory.UsagePercent)2122 $command.ExecuteNonQuery()23 $connection.Close()24}
Enhancement 3: Slack Integration
Send alerts to Slack instead of email:
1function Send-SlackAlert {2 param (3 [string]$WebhookUrl,4 [hashtable]$SystemMetrics,5 [array]$CriticalIssues6 )78 $payload = @{9 text = "π¨ *System Alert: $($SystemMetrics.ComputerName)*"10 blocks = @(11 @{12 type = "section"13 text = @{14 type = "mrkdwn"15 text = "*Critical Issues Detected:*`n" + ($CriticalIssues -join "`n")16 }17 }18 )19 } | ConvertTo-Json -Depth 102021 Invoke-RestMethod -Uri $WebhookUrl -Method Post -Body $payload -ContentType "application/json"22}
Troubleshooting
Issue 1: Permission denied errors
- Solution: Run PowerShell as Administrator
- Or: Adjust execution policy:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Issue 2: Email sending fails
- Gmail: Use App Password, not regular password
- Office 365: May need to enable SMTP AUTH in admin center
- Check firewall isn't blocking port 587
Issue 3: Scheduled task doesn't run
- Verify task is enabled:
Get-ScheduledTask -TaskName "SystemMonitoring" - Check task history in Task Scheduler GUI
- Ensure script paths are absolute (not relative)
Issue 4: High CPU from monitoring script itself
- Increase monitoring interval (10-15 minutes instead of 5)
- Reduce number of CPU samples in Get-SystemMetrics
- Skip network statistics collection (can be slow)
Real-World ROI
Before automation:
- Manual checks: 15 minutes, 3x per day = 45 min/day
- Incident response delay: 30-60 minutes (didn't know there was an issue)
- Monthly time spent: 15+ hours
After automation:
- Active monitoring time: 5 minutes/day reviewing dashboard
- Incident response: Immediate (email alert when threshold exceeded)
- Monthly time spent: 2.5 hours
Time saved: 12.5 hours per month = 150 hours per year
At $75/hour IT rate, that's $11,250 in reclaimed time annually.
Plus the value of preventing downtime before customers notice.
Frequently Asked Questions
Can this monitor Linux servers? This script is Windows-specific (PowerShell + WMI/CIM). For Linux, use similar concepts with Bash + SNMP or Python scripts. Or use PowerShell 7 on Linux with adapted commands.
How do I monitor SQL Server performance specifically?
Add SQL-specific metrics using Invoke-Sqlcmd:
1Invoke-Sqlcmd -Query "SELECT * FROM sys.dm_os_performance_counters WHERE counter_name = 'Page life expectancy'"
Can I view the dashboard in real-time without opening files?
Yes, host the HTML on IIS. Update a file called index.html with each run, and browse to http://servername/monitoring/.
What if I don't have an email server? Use a service like SendGrid (free tier available), or post to Slack/Teams webhooks, or write to a shared network folder that people can check.
How do I add custom metrics (like application-specific checks)?
Extend Get-SystemMetrics with your own checks. For example, check if a specific process is running, or query your application's health endpoint.
Related articles: PowerShell Active Directory Automation, Automate Windows Cleanup with PowerShell
Sponsored Content
Interested in advertising? Reach automation professionals through our platform.
