Managed IT • Knoxville, TN
Uptime-Based Reboot
automation
dateSep 5, 2024
statusRESOLVED
Incident

Compliance audit flagged a healthcare client for "missing critical patches." 23 of 47 workstations showed patches downloaded but not installed. Average fleet uptime was 34 days - users were hibernating laptops but never rebooting. One machine had been "up" for 97 days. Patches were there, just waiting for a reboot that never came.

Problem Identification
uptime analysis:
highest uptime ····· 97 days (front desk - "never turn it off")
average uptime ····· 34 days across 47 workstations
compliance status ·· 0% - all "installed" patches pending reboot
root cause ········ Fast Startup + user behavior

Windows 10's Fast Startup was the culprit. When users clicked "Shut Down," Windows actually hibernated the kernel. The machine appeared to power off, but it wasn't a true reboot. Updates in "Pending Reboot" status never installed.

Why Standard Solutions Failed
[X] Active Hours ······ users set 6 AM - 11 PM (never restart)
[X] Deadline forcing ·· triggers at 2 AM, laptops sleeping
[X] User prompts ······ "Restart now" dismissed for weeks
[X] Smart scheduling ·· picks inactive times when laptop asleep

Fundamental problem: Windows Update can only restart a machine that's awake and online. Users who close laptop lids at 5 PM and open at 8 AM never hit the maintenance window.

Solution

Enforce reboots through RMM agent, which runs with SYSTEM privileges and can force restarts regardless of Windows Update's "smart" scheduling.

[+] maintenance_reboot.ps1GitHub
$ErrorActionPreference = 'Stop'

<#
██╗     ██╗███╗   ███╗███████╗██╗  ██╗ █████╗ ██╗    ██╗██╗  ██╗
██║     ██║████╗ ████║██╔════╝██║  ██║██╔══██╗██║    ██║██║ ██╔╝
██║     ██║██╔████╔██║█████╗  ███████║███████║██║ █╗ ██║█████╔╝
██║     ██║██║╚██╔╝██║██╔══╝  ██╔══██║██╔══██║██║███╗██║██╔═██╗
███████╗██║██║ ╚═╝ ██║███████╗██║  ██║██║  ██║╚███╔███╔╝██║  ██╗
╚══════╝╚═╝╚═╝     ╚═╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═╝
================================================================================
 SCRIPT   : Maintenance Reboot                                            v8.1.2
 AUTHOR   : Limehawk.io
 DATE     : January 2026
 USAGE    : .\maintenance_reboot.ps1
================================================================================
 FILE     : maintenance_reboot.ps1
 DESCRIPTION : System reboot with graceful or force mode for maintenance
--------------------------------------------------------------------------------
 README
--------------------------------------------------------------------------------
 PURPOSE

   Performs system reboot when uptime exceeds threshold or reboot flags are
   detected. Supports two modes:

   GRACEFUL MODE (default) - For servers/workstations with active users
   - Warns users before rebooting (configurable delay)
   - Skips reboot if critical apps running (QuickBooks, SQL, etc.)
   - Apps get chance to save data and close cleanly

   FORCE MODE - For after-hours maintenance windows
   - Immediate forced reboot, no warning
   - Guaranteed to reboot regardless of running apps
   - Use when machine is known to be empty

 DATA SOURCES & PRIORITY
 1) RMM literal text replacement ($maxuptimedays)
 2) Environment variable (MAXUPTIMEDAYS)
 3) Default (7 days)

 REQUIRED INPUTS
 - Graceful Reboot:
   - RMM Name: '$graceful_true_or_false' (literal text placeholder)
   - Values: 'true' or 'false'
   - Default: true (graceful mode)

 - Max Uptime Days:
   - RMM Name: '$maxuptimedays' (literal text placeholder)
   - Env Name: 'MAXUPTIMEDAYS'
   - Constraints: Integer (1 or greater)
   - Default: 7

 SETTINGS (hardcoded in script)
 - $GracefulReboot        : Use graceful mode with warning (default: true)
 - $WarningMinutes        : Minutes of warning before graceful reboot (default: 5)
 - $CheckCriticalApps     : Skip reboot if critical apps running (default: true)
 - $CriticalProcessPatterns : Process names to check (QuickBooks, SQL, etc.)
 - $DefaultMaxUptimeDays  : Default uptime threshold if RMM/env not set (7)
 - $CheckCBSRebootPending : Check Component Based Servicing flag (default: true)
 - $CheckWURebootRequired : Check Windows Update flag (default: true)
 - $CheckPendingFileRename: Check Pending File Rename Operations (default: true)

 BEHAVIOR
 - Retrieves the system's last boot time to calculate current uptime in days
 - Checks Windows registry for reboot-pending flags
 - In graceful mode: checks for critical apps, skips if running
 - In graceful mode: schedules reboot with user warning
 - In force mode: immediate forced reboot
 - If neither uptime nor flags trigger, no action taken

 PREREQUISITES
 - PowerShell 5.1+
 - Must be run with local Administrator rights

 SECURITY NOTES
 - The script does not handle secrets or API keys
 - Graceful mode allows apps to save data before closing
 - Force mode will not prompt users - use for empty machines only

 ENDPOINTS
 - Not applicable. This script performs local actions only.

 EXIT CODES
 - 0 success (reboot initiated, skipped due to critical apps, or not needed)
 - 1 failure (input validation failed or command failed)

 EXAMPLE RUN - Graceful mode with critical app detected

 [INFO] INPUT VALIDATION
 ==============================================================
 Reboot Mode              : Graceful
 Warning Minutes          : 5
 Check Critical Apps      : Yes
 Max Uptime Days          : 7

 [INFO] CRITICAL APP CHECK
 ==============================================================
 Checking for critical processes...
 Critical Apps Running    : QBW32
 SKIPPING REBOOT - Critical applications are running

 [WARN] FINAL STATUS
 ==============================================================
 REBOOT SKIPPED (Critical applications running)

 [OK] SCRIPT COMPLETED
 ==============================================================

 EXAMPLE RUN - Force mode reboot triggered

 [INFO] INPUT VALIDATION
 ==============================================================
 Reboot Mode              : Force
 Max Uptime Days          : 7

 [INFO] REBOOT FLAG CHECK
 ==============================================================
 CBS RebootPending        : Yes
 WU RebootRequired        : No
 PendingFileRename        : No
 Reboot Flags Detected    : Yes

 [INFO] UPTIME CHECK
 ==============================================================
 Last Boot Time           : 2025-12-20T03:00:00
 Current Uptime (Days)    : 8
 Threshold (Days)         : 7
 Uptime Exceeded          : Yes

 [RUN] REBOOT ACTION
 ==============================================================
 Trigger                  : Both: Reboot flags AND uptime exceeded
 Method                   : Force (immediate)
 Result                   : INITIATING REBOOT

 [OK] FINAL STATUS
 ==============================================================
 OPERATION COMPLETED SUCCESSFULLY (REBOOT INITIATED)

 [OK] SCRIPT COMPLETED
 ==============================================================
--------------------------------------------------------------------------------
 CHANGELOG
--------------------------------------------------------------------------------
 2026-01-19 v8.1.2 Updated to two-line ASCII console output style
 2026-01-16 v8.1.1 Restored runtime variables to YAML metadata
 2025-12-28 v8.1.0 Added $graceful_true_or_false RMM runtime variable for mode selection
 2025-12-28 v8.0.0 Added graceful/force mode toggle, critical app checks, warning delay
 2025-12-28 v7.2.4 Removed template placeholder cruft
 2025-12-28 v7.2.3 Fixed WMI uptime query - replaced legacy [WMI] moniker with Get-CimInstance
 2025-12-23 v7.2.2 Updated to Limehawk Script Framework
 2025-12-18 v7.2.1 Renamed from invoke_reboot_on_uptime.ps1 to maintenance_reboot.ps1
 2025-12-15 v7.2.0 Simplified configuration; Added toggle settings for each reboot flag check
 2025-12-15 v7.0.0 Added reboot flag detection
 2025-09-25 v6.0.0 Reverted conditional logic to robust IF/ELSEIF/ELSE structure
================================================================================
#>

Set-StrictMode -Version Latest

# ==== REBOOT MODE SETTINGS ====
$RMMGracefulValue     = "`$graceful_true_or_false"
$DefaultGraceful      = $true
$WarningMinutes       = 5
$CheckCriticalApps    = $true

# Critical process names - reboot skipped if any are running (graceful mode only)
# QuickBooks: QBW32, QBW64, QBDBMgrN, QBCFMonitorService
# Databases: sqlservr, mysqld, postgres, mongod
$CriticalProcessPatterns = @(
    "QBW32",
    "QBW64",
    "QBDBMgrN",
    "QBCFMonitorService",
    "sqlservr",
    "mysqld",
    "postgres",
    "mongod"
)

# ==== UPTIME AND FLAG SETTINGS ====
$DefaultMaxUptimeDays   = 7
$CheckCBSRebootPending  = $true
$CheckWURebootRequired  = $true
$CheckPendingFileRename = $true
$RMMValue               = "`$maxuptimedays"

# ==== HELPER FUNCTIONS ====
function Write-Section {
    param([string]$title, [string]$status = "INFO")
    Write-Host ""
    Write-Host ("[$status] $title")
    Write-Host ("=" * 62)
}

function PrintKV([string]$label, [string]$value) {
    $lbl = $label.PadRight(24)
    Write-Host (" {0} : {1}" -f $lbl, $value)
}

# ==== INPUT RESOLUTION (RMM -> Default) ====

# Resolve GracefulReboot (RMM -> Default)
$isGracefulRMMUsable = -not [string]::IsNullOrWhiteSpace($RMMGracefulValue) -and ($RMMGracefulValue -notmatch '^\$\w+$')
if ($isGracefulRMMUsable) {
    # RMM value provided - check if it's "true" or "false"
    $GracefulReboot = $RMMGracefulValue -match '^(true|1|yes)$'
}
else {
    $GracefulReboot = $DefaultGraceful
}

# Resolve MaxUptimeDays (RMM -> ENV -> Default)
$ResolvedInput = $null
$isRMMValueUsable = -not [string]::IsNullOrWhiteSpace($RMMValue) -and ($RMMValue -notmatch '^\$\w+$')

if ($isRMMValueUsable) {
    $ResolvedInput = $RMMValue
}
elseif (-not [string]::IsNullOrWhiteSpace($env:MAXUPTIMEDAYS)) {
    $ResolvedInput = $env:MAXUPTIMEDAYS
}
else {
    $ResolvedInput = $DefaultMaxUptimeDays
}

# ==== VALIDATE AND CAST ====
$errors = @()
if (-not ([int]::TryParse($ResolvedInput, [ref]$null) -and [int]$ResolvedInput -ge 1)) {
    $errors += "MaxUptimeDays must be an integer of 1 or greater. Value provided: '$ResolvedInput'."
}
$MaxUptimeDays = [int]$ResolvedInput

if ($errors.Count -gt 0) {
    Write-Section "ERROR OCCURRED" "ERROR"
    foreach ($e in $errors) { PrintKV "Message" $e }
    Write-Section "FINAL STATUS" "ERROR"
    Write-Host " INPUT VALIDATION FAILED"
    Write-Section "SCRIPT COMPLETED" "ERROR"
    exit 1
}

# ==== INPUT VALIDATION OUTPUT ====
Write-Section "INPUT VALIDATION"
PrintKV "Reboot Mode" $(if ($GracefulReboot) { "Graceful" } else { "Force" })
if ($GracefulReboot) {
    PrintKV "Warning Minutes" $WarningMinutes
    PrintKV "Check Critical Apps" $(if ($CheckCriticalApps) { "Yes" } else { "No" })
}
PrintKV "Max Uptime Days" $MaxUptimeDays
PrintKV "Check CBS Pending" $(if ($CheckCBSRebootPending) { "Yes" } else { "No" })
PrintKV "Check WU Required" $(if ($CheckWURebootRequired) { "Yes" } else { "No" })
PrintKV "Check File Rename" $(if ($CheckPendingFileRename) { "Yes" } else { "No" })

# ==== MAIN OPERATION ====
try {
    # --- Critical Application Check (Graceful Mode Only) ---
    if ($GracefulReboot -and $CheckCriticalApps) {
        Write-Section "CRITICAL APP CHECK" "INFO"
        Write-Host " Checking for critical processes..."

        $runningCritical = @()
        $allProcesses = Get-Process -ErrorAction SilentlyContinue

        foreach ($pattern in $CriticalProcessPatterns) {
            $found = $allProcesses | Where-Object { $_.ProcessName -like "*$pattern*" }
            if ($found) {
                foreach ($proc in $found) {
                    $runningCritical += $proc.ProcessName
                }
            }
        }

        if ($runningCritical.Count -gt 0) {
            $uniqueProcesses = $runningCritical | Select-Object -Unique
            PrintKV "Critical Apps Running" ($uniqueProcesses -join ", ")
            Write-Host ""
            Write-Host " SKIPPING REBOOT - Critical applications are running"
            Write-Host " These applications may have unsaved data or open databases."
            Write-Host " Reboot will be attempted on next scheduled run."

            Write-Section "FINAL STATUS" "WARN"
            Write-Host " REBOOT SKIPPED (Critical applications running)"

            Write-Section "SCRIPT COMPLETED" "OK"
            exit 0
        }

        Write-Host " No critical applications running"
    }

    # --- Reboot Flag Detection ---
    Write-Section "REBOOT FLAG CHECK" "INFO"

    $cbsRebootPending = $false
    if ($CheckCBSRebootPending) {
        $cbsRebootPending = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
    }

    $wuRebootRequired = $false
    if ($CheckWURebootRequired) {
        $wuRebootRequired = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
    }

    $pendingFileRename = $false
    if ($CheckPendingFileRename) {
        $sessionManagerPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
        if (Test-Path $sessionManagerPath) {
            $pendingOps = Get-ItemProperty -Path $sessionManagerPath -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue
            if ($pendingOps -and $pendingOps.PendingFileRenameOperations) {
                $pendingFileRename = $true
            }
        }
    }

    $rebootFlagsDetected = $cbsRebootPending -or $wuRebootRequired -or $pendingFileRename

    PrintKV "CBS RebootPending" $(if (-not $CheckCBSRebootPending) { "Skipped" } elseif ($cbsRebootPending) { "Yes" } else { "No" })
    PrintKV "WU RebootRequired" $(if (-not $CheckWURebootRequired) { "Skipped" } elseif ($wuRebootRequired) { "Yes" } else { "No" })
    PrintKV "PendingFileRename" $(if (-not $CheckPendingFileRename) { "Skipped" } elseif ($pendingFileRename) { "Yes" } else { "No" })
    PrintKV "Reboot Flags Detected" $(if ($rebootFlagsDetected) { "Yes" } else { "No" })

    # --- Uptime Check ---
    Write-Section "UPTIME CHECK" "INFO"

    $os = Get-CimInstance Win32_OperatingSystem
    $bootTime = $os.LastBootUpTime
    $uptimeDays = ((Get-Date) - $bootTime).Days
    $bootTimeString = $bootTime.ToString('yyyy-MM-ddTHH:mm:ss')

    PrintKV "Last Boot Time" $bootTimeString
    PrintKV "Current Uptime (Days)" $uptimeDays
    PrintKV "Threshold (Days)" $MaxUptimeDays

    $uptimeExceeded = $uptimeDays -gt $MaxUptimeDays
    PrintKV "Uptime Exceeded" $(if ($uptimeExceeded) { "Yes" } else { "No" })

    # --- Reboot Decision ---
    if ($rebootFlagsDetected -or $uptimeExceeded) {
        Write-Section "REBOOT ACTION" "RUN"

        # Determine trigger reason
        if ($rebootFlagsDetected -and $uptimeExceeded) {
            PrintKV "Trigger" "Both: Reboot flags AND uptime exceeded"
        }
        elseif ($rebootFlagsDetected) {
            PrintKV "Trigger" "Reboot flags detected"
        }
        else {
            PrintKV "Trigger" "Uptime exceeds threshold"
        }

        if ($GracefulReboot) {
            # Graceful: schedule shutdown with warning
            $warningSeconds = $WarningMinutes * 60
            $shutdownMessage = "System will restart for maintenance in $WarningMinutes minutes. Please save your work."

            PrintKV "Method" "Graceful ($WarningMinutes min warning)"
            PrintKV "Result" "SCHEDULING REBOOT"

            $shutdownArgs = "/r /t $warningSeconds /c `"$shutdownMessage`" /d p:0:0"
            Start-Process -FilePath "shutdown.exe" -ArgumentList $shutdownArgs -NoNewWindow -Wait

            Write-Section "FINAL STATUS" "OK"
            Write-Host " OPERATION COMPLETED SUCCESSFULLY (REBOOT SCHEDULED IN $WarningMinutes MINUTES)"
        }
        else {
            # Force: immediate reboot
            PrintKV "Method" "Force (immediate)"
            PrintKV "Result" "INITIATING REBOOT"

            Restart-Computer -Force -ErrorAction Stop

            Write-Section "FINAL STATUS" "OK"
            Write-Host " OPERATION COMPLETED SUCCESSFULLY (REBOOT INITIATED)"
        }
    }
    else {
        Write-Section "FINAL STATUS" "OK"
        Write-Host " NO REBOOT REQUIRED. Uptime within threshold and no pending flags."
    }

    Write-Section "SCRIPT COMPLETED" "OK"
    exit 0
}
catch {
    Write-Section "ERROR OCCURRED" "ERROR"
    PrintKV "Step" "Main Operation"
    PrintKV "Error Type" $_.Exception.GetType().Name
    PrintKV "Error Message" $_.Exception.Message

    Write-Section "FINAL STATUS" "ERROR"
    Write-Host " OPERATION FAILED"

    Write-Section "SCRIPT COMPLETED" "ERROR"
    exit 1
}
Why This Works
technical details:
Win32_OperatingSystem · real LastBootUpTime (immune to Fast Startup)
quser enumeration ····· logs who was on before reboot
$SkipIfUserActive ····· optional defer if someone working
warning period ········ configurable 5-min warning for user

Key insight: RMM agent can wake machine from sleep to run scheduled tasks. Combined with this script, machines that "never reboot" now reboot weekly - automatically, during off-hours, with no user action required.

Outcome
metric before after
avg uptime ···· 34 days ··· 4 days
compliance ···· 0% ········ 100%
audit result ·· failed ···· passed

Deployed with 7-day threshold. Within one week, every machine had rebooted at least once. Patches that had been "pending reboot" for months finally installed. Re-ran compliance scan: 100% compliant.

takeaways:
"Shut Down" doesn't mean reboot - Fast Startup hibernates
WU deadlines only work if machines awake during maintenance
RMM-based enforcement catches what Windows Update misses
uptime is proxy for patch compliance - high uptime = low install
Get Help

Need automated patch compliance? We implement maintenance automation that keeps your fleet patched without disrupting users.

Contact Us