Automate Your
Morning IT
Checklist

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.

PowerShell ping-check.ps1
# 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
Server Status Timestamp
------ ------ ---------
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
💡 Tip
-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.

PowerShell disk-check.ps1
$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.

PowerShell service-check.ps1
# 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
    }
}
⚠ Warning
Be cautious with auto-restart on database services. SQL Server and similar services may have a reason for being stopped. Consider logging only and restarting manually, or limit auto-restart to web/app tier services.

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.

PowerShell eventlog-check.ps1
$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.

PowerShell build-report.ps1
$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.

PowerShell send-report.ps1
# 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
ℹ Info
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.

// Putting It All Together

Combine all steps into a single Morning-ITCheck.ps1 and register it with Task Scheduler to run at 07:45 every weekday.

PowerShell schedule-task.ps1
$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 $diskWarnings into a Teams webhook for instant channel alerts
  • Store credentials securely using SecretManagement module vaults
  • Log all results to a CSV for 30-day trending with Export-Csv -Append
  • Convert the HTML report into a PDF with wkhtmltopdf and attach it