Skip to content

Get-PendingReboot.ps1

Détection des redémarrages en attente sur Windows.


Description

  • Vérifie toutes les sources de reboot pending
  • Windows Update, CBS, File Rename, Configuration Manager
  • Support serveurs locaux et distants
  • Export JSON pour automation
  • Codes de retour pour intégration CI/CD

Prérequis

  • Système : Windows Server 2016+ ou Windows 10/11
  • PowerShell : Version 5.1 minimum
  • Permissions : Droits administrateur pour lire les clés Registry
  • Modules : Aucun module externe requis (PSRemoting pour serveurs distants)

Cas d'Usage

  • Maintenance préventive : Vérifier quels serveurs nécessitent un redémarrage avant patching
  • Monitoring : Intégration avec Nagios, Zabbix ou PRTG pour alertes automatiques
  • Planification : Identifier les fenêtres de maintenance nécessaires
  • CI/CD : Validation d'état avant déploiement en production

Utilisation

# Vérifier le serveur local
.\Get-PendingReboot.ps1

# Vérifier serveurs distants
.\Get-PendingReboot.ps1 -ComputerName "SRV01","SRV02","SRV03"

# Export JSON
.\Get-PendingReboot.ps1 -ComputerName (Get-Content servers.txt) -OutputFormat JSON

# Mode silencieux (code retour uniquement)
.\Get-PendingReboot.ps1 -Quiet

Paramètres

Paramètre Type Défaut Description
-ComputerName String[] localhost Serveurs à vérifier
-Credential PSCredential - Credentials pour accès distant
-OutputFormat String Table Format (Table, JSON, CSV)
-Quiet Switch - Pas d'output, code retour uniquement

Code Source

#Requires -Version 5.1
<#
.SYNOPSIS
    Detect pending reboots on Windows systems.

.DESCRIPTION
    Checks multiple sources for pending reboot indicators including Windows Update,
    Component Based Servicing, pending file renames, Computer Rename, and
    Configuration Manager client.

.PARAMETER ComputerName
    One or more computer names to check. Defaults to local machine.

.PARAMETER Credential
    Credentials for remote computer access.

.PARAMETER OutputFormat
    Output format: Table, JSON, or CSV.

.PARAMETER Quiet
    Suppress output, only return exit code (0=no reboot, 1=reboot pending).

.EXAMPLE
    .\Get-PendingReboot.ps1 -ComputerName "SRV01","SRV02"
    Check pending reboot status on multiple servers.

.NOTES
    Author: ShellBook
    Version: 1.0
    Date: 2024-01-01
#>

[CmdletBinding()]
param(
    [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [Alias('CN', 'Server', 'Name')]
    [string[]]$ComputerName = $env:COMPUTERNAME,

    [Parameter()]
    [System.Management.Automation.PSCredential]
    [System.Management.Automation.Credential()]
    $Credential = [System.Management.Automation.PSCredential]::Empty,

    [Parameter()]
    [ValidateSet('Table', 'JSON', 'CSV')]
    [string]$OutputFormat = 'Table',

    [Parameter()]
    [switch]$Quiet
)

#region Configuration
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
#endregion

#region Functions
function Write-Log {
    param(
        [string]$Message,
        [ValidateSet('Info', 'Warning', 'Error', 'Success')]
        [string]$Level = 'Info'
    )

    if ($Quiet) { return }

    $colors = @{
        'Info'    = 'Cyan'
        'Warning' = 'Yellow'
        'Error'   = 'Red'
        'Success' = 'Green'
    }

    Write-Host "[$(Get-Date -Format 'HH:mm:ss')] $Message" -ForegroundColor $colors[$Level]
}

function Test-PendingRebootLocal {
    <#
    .SYNOPSIS
        Check all pending reboot sources on local machine.
    #>
    $result = [PSCustomObject]@{
        ComputerName              = $env:COMPUTERNAME
        WindowsUpdate             = $false
        CBSRebootPending          = $false
        PendingFileRename         = $false
        PendingComputerRename     = $false
        CCMClientPending          = $false
        RebootRequired            = $false
        RebootRequiredReason      = [System.Collections.ArrayList]::new()
        LastBootTime              = $null
        CheckTime                 = Get-Date
        Error                     = $null
    }

    try {
        # Get last boot time
        $os = Get-CimInstance -ClassName Win32_OperatingSystem
        $result.LastBootTime = $os.LastBootUpTime

        # Check Windows Update
        $wuKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
        if (Test-Path $wuKey) {
            $result.WindowsUpdate = $true
            [void]$result.RebootRequiredReason.Add("Windows Update")
        }

        # Check Component Based Servicing
        $cbsKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
        if (Test-Path $cbsKey) {
            $result.CBSRebootPending = $true
            [void]$result.RebootRequiredReason.Add("Component Based Servicing")
        }

        # Check Pending File Rename Operations
        $pfroKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
        $pfroValue = Get-ItemProperty -Path $pfroKey -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue
        if ($pfroValue.PendingFileRenameOperations) {
            $result.PendingFileRename = $true
            [void]$result.RebootRequiredReason.Add("Pending File Rename")
        }

        # Check Pending Computer Rename
        $activeNameKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName'
        $pendingNameKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName'

        $activeName = (Get-ItemProperty -Path $activeNameKey -Name 'ComputerName' -ErrorAction SilentlyContinue).ComputerName
        $pendingName = (Get-ItemProperty -Path $pendingNameKey -Name 'ComputerName' -ErrorAction SilentlyContinue).ComputerName

        if ($activeName -ne $pendingName) {
            $result.PendingComputerRename = $true
            [void]$result.RebootRequiredReason.Add("Computer Rename: $activeName -> $pendingName")
        }

        # Check Configuration Manager (SCCM/MECM)
        try {
            $ccmSdk = [wmiclass]'\\.\root\ccm\clientsdk:CCM_ClientUtilities'
            $ccmReboot = $ccmSdk.DetermineIfRebootPending()
            if ($ccmReboot.RebootPending) {
                $result.CCMClientPending = $true
                [void]$result.RebootRequiredReason.Add("Configuration Manager")
            }
        }
        catch {
            # CCM not installed - ignore
        }

        # Set overall status
        $result.RebootRequired = $result.WindowsUpdate -or
                                 $result.CBSRebootPending -or
                                 $result.PendingFileRename -or
                                 $result.PendingComputerRename -or
                                 $result.CCMClientPending
    }
    catch {
        $result.Error = $_.Exception.Message
    }

    return $result
}

function Test-PendingRebootRemote {
    param(
        [string]$Computer,
        [pscredential]$Cred
    )

    $scriptBlock = {
        $result = [PSCustomObject]@{
            ComputerName              = $env:COMPUTERNAME
            WindowsUpdate             = $false
            CBSRebootPending          = $false
            PendingFileRename         = $false
            PendingComputerRename     = $false
            CCMClientPending          = $false
            RebootRequired            = $false
            RebootRequiredReason      = @()
            LastBootTime              = $null
            CheckTime                 = Get-Date
            Error                     = $null
        }

        try {
            $os = Get-CimInstance -ClassName Win32_OperatingSystem
            $result.LastBootTime = $os.LastBootUpTime

            # Windows Update
            if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') {
                $result.WindowsUpdate = $true
                $result.RebootRequiredReason += "Windows Update"
            }

            # CBS
            if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') {
                $result.CBSRebootPending = $true
                $result.RebootRequiredReason += "Component Based Servicing"
            }

            # Pending File Rename
            $pfro = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue
            if ($pfro.PendingFileRenameOperations) {
                $result.PendingFileRename = $true
                $result.RebootRequiredReason += "Pending File Rename"
            }

            # Computer Rename
            $active = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName' -Name 'ComputerName' -ErrorAction SilentlyContinue).ComputerName
            $pending = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName' -Name 'ComputerName' -ErrorAction SilentlyContinue).ComputerName
            if ($active -ne $pending) {
                $result.PendingComputerRename = $true
                $result.RebootRequiredReason += "Computer Rename"
            }

            # CCM
            try {
                $ccm = [wmiclass]'\\.\root\ccm\clientsdk:CCM_ClientUtilities'
                if ($ccm.DetermineIfRebootPending().RebootPending) {
                    $result.CCMClientPending = $true
                    $result.RebootRequiredReason += "Configuration Manager"
                }
            } catch {}

            $result.RebootRequired = $result.WindowsUpdate -or $result.CBSRebootPending -or
                                     $result.PendingFileRename -or $result.PendingComputerRename -or
                                     $result.CCMClientPending
        }
        catch {
            $result.Error = $_.Exception.Message
        }

        return $result
    }

    $invokeParams = @{
        ComputerName = $Computer
        ScriptBlock  = $scriptBlock
        ErrorAction  = 'Stop'
    }

    if ($Cred -ne [System.Management.Automation.PSCredential]::Empty) {
        $invokeParams.Credential = $Cred
    }

    try {
        $result = Invoke-Command @invokeParams
        return $result
    }
    catch {
        return [PSCustomObject]@{
            ComputerName              = $Computer
            WindowsUpdate             = $null
            CBSRebootPending          = $null
            PendingFileRename         = $null
            PendingComputerRename     = $null
            CCMClientPending          = $null
            RebootRequired            = $null
            RebootRequiredReason      = @()
            LastBootTime              = $null
            CheckTime                 = Get-Date
            Error                     = $_.Exception.Message
        }
    }
}

function Format-Output {
    param(
        [object[]]$Data,
        [string]$Format
    )

    switch ($Format) {
        'Table' {
            $Data | Format-Table -AutoSize @(
                'ComputerName',
                @{N='Reboot?'; E={
                    if ($_.RebootRequired) {
                        'YES'
                    } elseif ($null -eq $_.RebootRequired) {
                        'ERROR'
                    } else {
                        'NO'
                    }
                }},
                @{N='WU'; E={if ($_.WindowsUpdate) { 'X' } else { '-' }}},
                @{N='CBS'; E={if ($_.CBSRebootPending) { 'X' } else { '-' }}},
                @{N='File'; E={if ($_.PendingFileRename) { 'X' } else { '-' }}},
                @{N='Name'; E={if ($_.PendingComputerRename) { 'X' } else { '-' }}},
                @{N='CCM'; E={if ($_.CCMClientPending) { 'X' } else { '-' }}},
                @{N='LastBoot'; E={if ($_.LastBootTime) { $_.LastBootTime.ToString('yyyy-MM-dd HH:mm') } else { 'N/A' }}},
                'Error'
            )
        }
        'JSON' {
            $Data | ConvertTo-Json -Depth 5
        }
        'CSV' {
            $Data | Select-Object ComputerName, RebootRequired,
                @{N='Reasons'; E={$_.RebootRequiredReason -join '; '}},
                WindowsUpdate, CBSRebootPending, PendingFileRename,
                PendingComputerRename, CCMClientPending, LastBootTime, Error |
            ConvertTo-Csv -NoTypeInformation
        }
    }
}
#endregion

#region Main
$allResults = [System.Collections.ArrayList]::new()
$rebootPending = $false

Write-Log "=== Pending Reboot Check ===" -Level Info
Write-Log "Checking $($ComputerName.Count) computer(s)..." -Level Info
Write-Log ""

foreach ($computer in $ComputerName) {
    Write-Log "Checking: $computer" -Level Info

    if ($computer -eq $env:COMPUTERNAME -or $computer -eq 'localhost' -or $computer -eq '.') {
        $result = Test-PendingRebootLocal
    } else {
        $result = Test-PendingRebootRemote -Computer $computer -Cred $Credential
    }

    [void]$allResults.Add($result)

    if ($result.RebootRequired) {
        $rebootPending = $true
        Write-Log "  REBOOT REQUIRED: $($result.RebootRequiredReason -join ', ')" -Level Warning
    } elseif ($result.Error) {
        Write-Log "  ERROR: $($result.Error)" -Level Error
    } else {
        Write-Log "  No reboot pending" -Level Success
    }
}

Write-Log ""

if (-not $Quiet) {
    Format-Output -Data $allResults -Format $OutputFormat

    Write-Log ""
    Write-Log "=== Summary ===" -Level Info
    $pendingCount = ($allResults | Where-Object { $_.RebootRequired -eq $true }).Count
    $okCount = ($allResults | Where-Object { $_.RebootRequired -eq $false }).Count
    $errorCount = ($allResults | Where-Object { $_.Error }).Count

    Write-Log "Pending: $pendingCount | OK: $okCount | Errors: $errorCount" -Level Info
}

# Exit code
if ($rebootPending) {
    exit 1
} else {
    exit 0
}
#endregion

Exemples de Sortie

Table Output

ComputerName Reboot? WU CBS File Name CCM LastBoot           Error
------------ ------- -- --- ---- ---- --- --------           -----
SRV-DC01     NO      -  -   -    -    -   2024-01-10 08:00
SRV-SQL01    YES     X  -   -    -    -   2024-01-05 22:30
SRV-WEB01    YES     -  X   X    -    -   2024-01-08 14:00
SRV-APP01    ERROR   -  -   -    -    -   N/A                Access denied

JSON Output

[
  {
    "ComputerName": "SRV-SQL01",
    "WindowsUpdate": true,
    "CBSRebootPending": false,
    "PendingFileRename": false,
    "PendingComputerRename": false,
    "CCMClientPending": false,
    "RebootRequired": true,
    "RebootRequiredReason": ["Windows Update"],
    "LastBootTime": "2024-01-05T22:30:00",
    "CheckTime": "2024-01-15T10:00:00",
    "Error": null
  }
]

Sources Vérifiées

Source Clé Registry / WMI
Windows Update HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired
CBS HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending
File Rename HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations
Computer Rename Comparaison ActiveComputerName vs ComputerName
SCCM/MECM CCM_ClientUtilities.DetermineIfRebootPending()

Intégration CI/CD

Script de Maintenance

# Check before patching
$servers = Get-Content "servers.txt"
$pending = .\Get-PendingReboot.ps1 -ComputerName $servers -OutputFormat JSON | ConvertFrom-Json |
           Where-Object { $_.RebootRequired }

if ($pending) {
    Write-Warning "Ces serveurs nécessitent un reboot avant patching:"
    $pending.ComputerName
    exit 1
}

# Continue with patching...

Monitoring Nagios/Zabbix

# Agent check script
$result = .\Get-PendingReboot.ps1 -Quiet
exit $result  # 0 = OK, 1 = Reboot needed

Voir Aussi