1 Ping All Servers
The first thing any morning checklist should do is confirm which machines are alive. We'll define a list of servers and test connectivity in parallel using PowerShell jobs, collecting results into a structured object.
# Define your server list
$servers = @(
'web-01', 'web-02', 'db-primary',
'db-replica', 'fileserver', 'vpn-gateway'
)
$pingResults = $servers | ForEach-Object -Parallel {
$status = if (Test-Connection $_ -Count 1 -Quiet) { 'Online' } else { 'OFFLINE' }
[PSCustomObject]@{
Server = $_
Status = $status
Timestamp = Get-Date -Format 'HH:mm:ss'
}
} -ThrottleLimit 10
# Print a quick summary table
$pingResults | Format-Table -AutoSize
------ ------ ---------
web-01 Online 08:02:11
web-02 Online 08:02:11
db-primary Online 08:02:12
db-replica Online 08:02:12
fileserver Online 08:02:12
vpn-gateway OFFLINE 08:02:13
-Parallel requires PowerShell 7+. On PS 5.1, replace with a standard
ForEach-Object loop or use Start-Job / Receive-Job
for parallel execution.
2 Check Disk Space
Disk space is the silent killer. Query all fixed drives on every online server and flag any volume below your chosen threshold — 20% free is a sensible default.
$threshold = 20 # % free space — warn below this
$onlineBoxes = $pingResults | Where-Object Status -eq 'Online' |
Select-Object -ExpandProperty Server
$diskResults = Invoke-Command -ComputerName $onlineBoxes -ScriptBlock {
Get-PSDrive -PSProvider FileSystem |
Where-Object { $_.Used -gt 0 } |
Select-Object -Property @{
N='Server'; E={$env:COMPUTERNAME}},
@{N='Drive'; E={$_.Name}},
@{N='FreeGB'; E={[math]::Round($_.Free / 1GB, 1)}},
@{N='UsedGB'; E={[math]::Round($_.Used / 1GB, 1)}},
@{N='FreePct'; E={[math]::Round($_.Free / ($_.Used + $_.Free) * 100, 1)}}
}
# Flag drives below threshold
$diskWarnings = $diskResults | Where-Object { $_.FreePct -lt $threshold }
if ($diskWarnings) {
Write-Warning "$($diskWarnings.Count) drive(s) below ${threshold}% free!"
$diskWarnings | Format-Table -AutoSize
}
3 Verify Critical Services
Define the services that must be running on each server and check their state remotely. Stopped services get logged — and optionally auto-restarted.
# Map servers to the services they should be running
$serviceMap = @{
'web-01' = @('W3SVC', 'wuauserv')
'db-primary' = @('MSSQLSERVER', 'SQLAgent')
'fileserver' = @('LanmanServer', 'DFS')
}
$serviceResults = foreach ($server in $serviceMap.Keys) {
Invoke-Command -ComputerName $server -ScriptBlock {
param($svcList)
Get-Service -Name $svcList -ErrorAction SilentlyContinue |
Select-Object @{N='Server';E={$env:COMPUTERNAME}},
Name, DisplayName, Status
} -ArgumentList (,$serviceMap[$server])
}
# Auto-restart stopped services (optional — comment out if unwanted)
$stopped = $serviceResults | Where-Object Status -ne 'Running'
foreach ($svc in $stopped) {
Write-Warning "[$($svc.Server)] $($svc.Name) is $($svc.Status) — attempting restart"
Invoke-Command -ComputerName $svc.Server {
Start-Service -Name $using:svc.Name
}
}
4 Scan the Windows Event Log
Pull critical errors from the System and Application logs written in the last 12 hours. This catches overnight crashes, disk errors, and application faults you'd otherwise miss.
$lookbackHours = 12
$since = (Get-Date).AddHours(-$lookbackHours)
$eventResults = Invoke-Command -ComputerName $onlineBoxes -ScriptBlock {
param($since)
Get-WinEvent -FilterHashtable @{
LogName = 'System', 'Application'
Level = 1, 2 # 1=Critical, 2=Error
StartTime = $since
} -ErrorAction SilentlyContinue |
Select-Object -First 10 |
Select-Object @{N='Server';E={$env:COMPUTERNAME}},
TimeCreated, Id, LevelDisplayName,
@{N='Message';E={$_.Message.Substring(0,
[math]::Min(80, $_.Message.Length))}}
} -ArgumentList $since
5 Build an HTML Report
Consolidate all results into a clean HTML table using ConvertTo-Html with
inline CSS so the email renders correctly in Outlook, Gmail, and Apple Mail without
external stylesheets.
$css = @"
body { font-family: Consolas, monospace; background:#0a0c0f; color:#cdd5e0; }
table { border-collapse: collapse; width:100%; }
th { background:#111418; color:#00e676; text-align:left; padding:8px 12px; }
td { padding:6px 12px; border-bottom:1px solid #1e2530; }
.ok { color:#00e676 }
.warn { color:#ffab00 }
.err { color:#ff5252 }
"@
$offlineRows = $pingResults | Where-Object Status -eq 'OFFLINE'
$reportDate = Get-Date -Format 'dddd, MMMM d yyyy — HH:mm'
$html = @"
<html><head><style>$css</style></head><body>
<h2>🖥 Morning IT Report — $reportDate</h2>
<h3>Server Ping Summary</h3>
"@
# Colour-code Online / OFFLINE rows
$html += $pingResults | ConvertTo-Html -Fragment |
ForEach-Object {
$_ -replace '>Online<', ' class="ok">Online<' `
-replace '>OFFLINE<', ' class="err">OFFLINE<'
}
$html += '<h3>Disk Space</h3>'
$html += $diskResults | ConvertTo-Html -Fragment
$html += '<h3>Service Status</h3>'
$html += $serviceResults | ConvertTo-Html -Fragment
$html += '</body></html>'
6 Send the Report by Email
With the HTML report assembled, fire it off to your team. Use Send-MailMessage
for on-premises SMTP, or swap in the Microsoft Graph module for a modern O365 setup.
# Option A — On-prem SMTP relay (no auth needed on internal relay)
$mailParams = @{
SmtpServer = 'smtp.corp.local'
From = 'monitoring@corp.local'
To = 'it-team@corp.local'
Subject = "[IT] Morning Checklist — $(Get-Date -Format 'yyyy-MM-dd')"
Body = $html
BodyAsHtml = $true
}
Send-MailMessage @$mailParams
# Option B — Microsoft 365 with stored credential
$cred = Get-Credential # or load from a secrets vault
Send-MailMessage @$mailParams -SmtpServer 'smtp.office365.com' `
-Port 587 -UseSsl -Credential $cred
Send-MailMessage is marked obsolete in PS 7 due to lack of TLS 1.2 support
in some older builds. For production use, consider the
Microsoft.Graph.Mail module or a wrapper like MailKit via
Install-Package MailKit.
$action = New-ScheduledTaskAction -Execute 'pwsh.exe' `
-Argument '-NonInteractive -File "C:\Scripts\Morning-ITCheck.ps1"'
$trigger = New-ScheduledTaskTrigger -Weekly `
-DaysOfWeek Monday,Tuesday,Wednesday,Thursday,Friday `
-At '07:45'
$settings = New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable `
-StartWhenAvailable
Register-ScheduledTask -TaskName 'Morning IT Checklist' `
-Action $action -Trigger $trigger -Settings $settings `
-RunLevel Highest -Force
Next Steps
- Add SSL certificate expiry checks using
Test-NetConnection+X509Certificate2 - Pipe
$diskWarningsinto a Teams webhook for instant channel alerts - Store credentials securely using
SecretManagementmodule vaults - Log all results to a CSV for 30-day trending with
Export-Csv -Append - Convert the HTML report into a PDF with
wkhtmltopdfand attach it