PowerShell Uptime Report: Check Last Boot Time of Servers

PowerShell Uptime Report: Check Last Boot Time of Servers

PowerShell Tips Editor 2 min read
PowerShell Uptime Report: Check Last Boot Time of Servers

After Patch Tuesday, how do you confirm every server actually came back up and has been running since the expected reboot? Checking each server manually through RDP or SCCM is slow. A PowerShell server uptime script queries last boot time across all your servers in parallel, calculates uptime duration, flags anything that rebooted unexpectedly in the last 24 hours, and exports a structured report in under a minute. This post builds the complete uptime reporting solution using CIM instances.

Get Last Boot Time with CIM

Get-CimInstance with the Win32_OperatingSystem class returns the LastBootUpTime property as a proper .NET DateTime object — much easier to work with than the old WMI string format:

$os = Get-CimInstance -ClassName Win32_OperatingSystem
Write-Host "Last boot time: $($os.LastBootUpTime)"
Write-Host "System name: $($os.CSName)"
Last boot time: 5/2/2026 2:34:17 AM
System name: SERVER01

Calculate Uptime Duration

Subtract the last boot time from now to get the uptime as a TimeSpan, then format it readably:

function Get-ServerUptime {
    param([string]$ComputerName = $env:COMPUTERNAME)

    $os       = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName
    $uptime   = (Get-Date) - $os.LastBootUpTime
    $uptimeStr = "{0} days, {1:D2}h {2:D2}m" -f $uptime.Days, $uptime.Hours, $uptime.Minutes

    [PSCustomObject]@{
        Server       = $ComputerName
        LastBootTime = $os.LastBootUpTime
        Uptime       = $uptimeStr
        UptimeDays   = [Math]::Round($uptime.TotalDays, 1)
    }
}

Get-ServerUptime -ComputerName "Server01"
Server    LastBootTime            Uptime              UptimeDays
------    ------------            ------              ----------
Server01  5/2/2026 2:34:17 AM    2 days, 07h 22m     2.3

Check Multiple Servers

Query uptime across a server list using Invoke-Command for parallel execution, which is dramatically faster than sequential CIM calls:

$servers = Get-Content "C:\Scripts\servers.txt"

$uptimeResults = Invoke-Command -ComputerName $servers -ThrottleLimit 20 -ScriptBlock {
    $os     = Get-CimInstance -ClassName Win32_OperatingSystem
    $uptime = (Get-Date) - $os.LastBootUpTime
    [PSCustomObject]@{
        Server       = $env:COMPUTERNAME
        LastBootTime = $os.LastBootUpTime
        UptimeDays   = [Math]::Round($uptime.TotalDays, 1)
        UptimeStr    = "{0}d {1:D2}h {2:D2}m" -f $uptime.Days, $uptime.Hours, $uptime.Minutes
        OS           = $os.Caption
    }
} -ErrorAction SilentlyContinue

$uptimeResults | Sort-Object UptimeDays | Format-Table -AutoSize

Flag Servers Rebooted in Last 24 Hours

After a patch cycle, identify all servers that rebooted within the expected window and flag any that rebooted outside it (potential unexpected restarts):

$patchWindow     = [datetime]"2026-05-03 20:00:00"
$patchWindowEnd  = [datetime]"2026-05-04 06:00:00"
$now             = Get-Date

$recentReboots = $uptimeResults | Where-Object { $_.LastBootTime -gt $now.AddDays(-1) }
Write-Host "Servers rebooted in last 24 hours: $($recentReboots.Count)"

foreach ($svr in $recentReboots) {
    $inWindow = $svr.LastBootTime -ge $patchWindow -and $svr.LastBootTime -le $patchWindowEnd
    $flag = if ($inWindow) { "Expected (patch window)" } else { "UNEXPECTED REBOOT" }
    Write-Host "  $($svr.Server): $($svr.LastBootTime) — $flag"
}
Servers rebooted in last 24 hours: 3
  SERVER01: 5/3/2026 11:45 PM — Expected (patch window)
  SERVER02: 5/4/2026 2:12 AM — Expected (patch window)
  SERVER03: 5/4/2026 8:30 AM — UNEXPECTED REBOOT

Export Uptime Report to CSV

Export the full uptime data to CSV for inclusion in post-patching documentation or operational reports:

$reportPath = "C:\Reports\Uptime_$(Get-Date -Format 'yyyyMMdd_HHmm').csv"

$uptimeResults |
    Select-Object Server, LastBootTime, UptimeDays, UptimeStr, OS |
    Sort-Object UptimeDays |
    Export-Csv -Path $reportPath -NoTypeInformation

Write-Host "Uptime report: $reportPath ($($uptimeResults.Count) servers)"

Alert on Unexpected Reboots

Send an email alert when servers reboot outside the expected maintenance window:

$unexpected = $uptimeResults | Where-Object {
    $_.LastBootTime -gt (Get-Date).AddHours(-6) -and
    ($_.LastBootTime -lt $patchWindow -or $_.LastBootTime -gt $patchWindowEnd)
}

if ($unexpected) {
    $body = "Unexpected reboots detected:`n" +
        ($unexpected | ForEach-Object { "$($_.Server): $($_.LastBootTime)" } | Out-String)

    Send-MailMessage -From "[email protected]" -To "[email protected]" `
        -Subject "ALERT: Unexpected reboots on $($unexpected.Count) server(s)" `
        -Body $body -SmtpServer "smtp-relay.corp.com"
}

Common Errors and Fixes

  • Time zone differences affect uptime calculation. If servers are in different time zones, LastBootUpTime returns local time on the remote server. Use $os.LastBootUpTime.ToUniversalTime() before comparison when querying servers across time zones, then convert back to local time for display.
  • CIM query requires WinRM or DCOM access. Get-CimInstance -ComputerName uses WinRM by default. If WinRM is blocked, create a CIM session with DCOM fallback using New-CimSessionOption -Protocol Dcom. Always test connectivity with Test-WSMan -ComputerName $server before running bulk uptime queries.

Related Cmdlets / See Also

Wrapping Up

Use Get-CimInstance Win32_OperatingSystem for last boot time, Invoke-Command with -ThrottleLimit for parallel multi-server queries, and compare boot times against known patch windows to separate expected from unexpected reboots. Schedule this script to run automatically after every Patch Tuesday maintenance window.

Send-Item -To