Test de redémarrage en attente sur les serveurs Windows avec PowerShell

Chaque fois que vous installez un logiciel, des mises à jour ou apportez des modifications de configuration, il est courant que Windows ait besoin d’un redémarrage. De nombreuses tâches du système d’exploitation obligent parfois Windows à nécessiter un redémarrage. Lorsqu’un redémarrage est nécessaire, Windows ajoute certaines valeurs au registre pour le montrer. Dans ce billet de blog, vous allez apprendre comment vérifier si un redémarrage est en attente et comment construire un script PowerShell pour automatiser la tâche.

**Windows a besoin d’être redémarré**

Lorsque vous êtes sur la console, vous pouvez remarquer qu’un redémarrage est en attente par une boîte contextuelle ou une notification comme indiqué ci-dessous.

À partir de cette notification, vous pouvez redémarrer Windows et en finir avec cela. Mais que faire si vous ne pouvez pas redémarrer immédiatement une machine lorsqu’elle en a besoin ? Et si vous venez d’installer des mises à jour sur un serveur de production et que ce serveur ne peut pas être redémarré tout de suite ?

Pending Windows reboot message

Le redémarrage doit attendre.

Le temps passe et d’ici là, le redémarrage peut être complètement oublié ! Au moment où vous vous en rendez compte, de nombreux serveurs ou postes de travail doivent être redémarrés, mais lesquels ?

Les indicateurs de redémarrage en attente se trouvent dans le Registre.

A pending reboot is defined in many places. Scroll right to see the values and conditions. A Windows computer is pending a reboot if any of the conditions in this table are true.

Key Value Condition
HKLM:\SOFTWARE\Microsoft\Updates UpdateExeVolatile Value is anything other than 0
HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager PendingFileRenameOperations value exists
HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager PendingFileRenameOperations2 value exists
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired NA key exists
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending NA Any GUID subkeys exist
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting NA key exists
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce DVDRebootSignal value exists
HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending NA key exists
HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootInProgress NA key exists
HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending NA key exists
HKLM:\SOFTWARE\Microsoft\ServerManager\CurrentRebootAttempts NA key exists
HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon JoinDomain value exists
HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon AvoidSpnSet value exists
HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName ComputerName Value ComputerName in HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName is different

Si vous avez le client Microsoft System Center Configuration Manager (SCCM) installé, vous pouvez également voir ces méthodes dans WMI.

Namespace Class Property Value Product Notes
ROOT\ccm\ClientSDK CCM_ClientUtilities DetermineifRebootPending RebootPending SCCM ReturnValue needs to be 0 and this value is not null
ROOT\ccm\ClientSDK CCM_ClientUtilities DetermineifRebootPending IsHardRebootPending SCCM ReturnValue needs to be 0 and this value is not null

Une fois que vous connaissez chaque méthode pour vérifier un redémarrage en attente, il existe de nombreuses façons différentes de vérifier les valeurs du registre. Vous pourriez ouvrir regedit.exe et parcourir manuellement chaque clé du registre.

Checking regedit manually

Vérifier manuellement via le registre fonctionne mais nous sommes humains. Et si vous oubliez de vérifier un chemin de registre ou simplement oubliez lesquels vérifier ? Il y a une bien meilleure façon de faire cela. Vous pouvez créer un script ou une fonction pour le faire à votre place. Dans mon cas, je préfère PowerShell, c’est donc ce que j’utiliserai.

En utilisant un script PowerShell, vous pouvez interroger un ou tous les ordinateurs de notre domaine ou fournir manuellement les noms des serveurs pour voir s’ils sont en attente d’un redémarrage. Vous pouvez alors décider de les redémarrer immédiatement ou de faire une liste pour les redémarrer plus tard. Le choix vous appartient.

Pour utiliser ma méthode PowerShell, vous devrez vous assurer que l’Accès distant PowerShell est configuré et disponible sur vos serveurs.

Tester pour un redémarrage en attente (La méthode facile)

Si vous ne voulez pas apprendre à vérifier ces clés de registre et à construire un outil comme celui-ci en PowerShell, je vous facilite la tâche. Il vous suffit d’ouvrir votre console PowerShell et de taper Install-Script Test-PendingReboot. Install-Script téléchargera mon script PowerShell depuis la galerie PowerShell vers C:\Program Files\WindowsPowerShell\Scripts. Ensuite, exécutez le script comme indiqué ci-dessous.

PS51> Test-PendingReboot.ps1 -ComputerName localhost

ComputerName IsPendingReboot
------------ ---------------
localhost              False

Vous pouvez fournir autant de serveurs que vous le souhaitez via le paramètre ComputerName. Le script renverra Vrai ou Faux ainsi que le nom du serveur.

Cet outil vérifie toutes les clés de registre dans le tableau ci-dessus pour vous.

Si vous souhaitez ajouter des conditions que j’ai omises ou corriger des erreurs que j’ai commises, n’hésitez pas à soumettre une demande de tirage sur GitHub pour la corriger.

Si vous souhaitez apprendre à créer un outil comme celui-ci, continuez à lire !

Construction d’un outil PowerShell de redémarrage en attente

Tout d’abord, vous devrez définir tous les ordinateurs sur lesquels vous souhaitez tester un redémarrage. Il existe de nombreuses façons différentes de le faire, mais pour cette démonstration, je les définirai manuellement via un tableau.

$servers = 'SRV1','SRV2','SRV3'

Créez maintenant une boucle foreach pour itérer sur chacun d’eux.

foreach ($computer in $ComputerName)

}

Ensuite, je vous recommande d’utiliser l’accès distant PowerShell et de vérifier chaque clé de registre et chaque condition de valeur à l’intérieur d’une seule session PS. Créez une session PS pour chaque serveur.

foreach ($computer in $ComputerName)
    $session = New-PSSession
}

Une fois que vous avez créé une session PS, vous devrez alors exécuter les vérifications.

Étant donné que vous allez exécuter de nombreuses vérifications différentes en utilisant le même code tel que :

  • Tester si une clé de registre existe
  • Tester si une valeur de registre existe
  • Tester si une valeur de registre n’est pas nulle

I recommend creating simple functions for each of these checks. This allows you to call a function instead of duplicating code. The Test-PendingReboot script builds all of these helper functions into a single scriptblock as shown below.

function Test-RegistryKey {
        [OutputType('bool')]
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Key
        )
    
        $ErrorActionPreference = 'Stop'

        if (Get-Item -Path $Key -ErrorAction Ignore) {
            $true
        }
    }

    function Test-RegistryValue {
        [OutputType('bool')]
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Key,

            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Value
        )
    
        $ErrorActionPreference = 'Stop'

        if (Get-ItemProperty -Path $Key -Name $Value -ErrorAction Ignore) {
            $true
        }
    }

    function Test-RegistryValueNotNull {
        [OutputType('bool')]
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Key,

            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Value
        )
    
        $ErrorActionPreference = 'Stop'

        if (($regVal = Get-ItemProperty -Path $Key -Name $Value -ErrorAction Ignore) -and $regVal.($Value)) {
            $true
        }
    }

À l’intérieur de ce même bloc de script, définissez chaque condition en référençant les fonctions d’aide que vous venez de créer.

$tests = @(
        { Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending' }
        { Test-RegistryKey -Key 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootInProgress' }
        { Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' }
        { Test-RegistryKey -Key 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending' }
        { Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting' }
        { Test-RegistryValueNotNull -Key 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations' }
        { Test-RegistryValueNotNull -Key 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations2' }
        { 
            # Ajout d'un test pour vérifier d'abord si la clé existe, l'utilisation de "ErrorAction ignore" renverra incorrectement $true
            'HKLM:\SOFTWARE\Microsoft\Updates' | Where-Object { test-path $_ -PathType Container } | ForEach-Object {            
                (Get-ItemProperty -Path $_ -Name 'UpdateExeVolatile' -ErrorAction Ignore | Select-Object -ExpandProperty UpdateExeVolatile) -ne 0 
            }
        }
        { Test-RegistryValue -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Value 'DVDRebootSignal' }
        { Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\ServerManager\CurrentRebootAttemps' }
        { Test-RegistryValue -Key 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon' -Value 'JoinDomain' }
        { Test-RegistryValue -Key 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon' -Value 'AvoidSpnSet' }
        {
            # Ajout d'un test pour vérifier d'abord si les clés existent, sinon chaque groupe renverra $Null
            # Il peut être nécessaire d'évaluer ce que cela signifie si l'une ou l'autre de ces clés n'existe pas
            ( 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName' | Where-Object { test-path $_ } | %{ (Get-ItemProperty -Path $_ ).ComputerName } ) -ne 
            ( 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName' | Where-Object { Test-Path $_ } | %{ (Get-ItemProperty -Path $_ ).ComputerName } )
        }
        {
            # Ajout d'un test pour vérifier d'abord si la clé existe
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object { 
                (Test-Path $_) -and (Get-ChildItem -Path $_) } | ForEach-Object { $true }
        }
    )

Vous pouvez maintenant créer une boucle foreach à l’intérieur de votre boucle $servers foreach qui lit chaque test et exécute chaque test.

foreach ($test in $tests) {
	if (& $test) {
		$true
		break
	}
}

Lorsque vous exécutez le code, le script renvoie une sortie comme ceci:

ComputerName IsPendingReboot
------------ ---------------
Server1              False
Server2              True

Vous pouvez créer cette sortie en vous assurant que la boucle foreach renvoie un seul objet par serveur. Vous devez savoir que si une des valeurs du registre existe, alors le serveur est en attente d’un redémarrage. Sachant cela, vous devez alors renvoyer True si l’une des valeurs existe et False si aucune d’entre elles n’existe.

Regroupez tout cela dans un script et cela devrait ressembler à ceci (avec quelques ajouts mineurs comme Credential).

[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string[]]$ComputerName,
	
    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [pscredential]$Credential
)

$ErrorActionPreference = 'Stop'

$scriptBlock = {

    $VerbosePreference = $using:VerbosePreference
    function Test-RegistryKey {
        [OutputType('bool')]
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Key
        )
    
        $ErrorActionPreference = 'Stop'

        if (Get-Item -Path $Key -ErrorAction Ignore) {
            $true
        }
    }

    function Test-RegistryValue {
        [OutputType('bool')]
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Key,

            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Value
        )
    
        $ErrorActionPreference = 'Stop'

        if (Get-ItemProperty -Path $Key -Name $Value -ErrorAction Ignore) {
            $true
        }
    }

    function Test-RegistryValueNotNull {
        [OutputType('bool')]
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Key,

            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Value
        )
    
        $ErrorActionPreference = 'Stop'

        if (($regVal = Get-ItemProperty -Path $Key -Name $Value -ErrorAction Ignore) -and $regVal.($Value)) {
            $true
        }
    }

    # Ajout de "test-path" à chaque test qui n'utilisait pas une fonction personnalisée ci-dessus depuis
    # une exception est levée lorsque Get-ItemProperty ou Get-ChildItem sont passés un chemin de clé inexistant
    $tests = @(
        { Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending' }
        { Test-RegistryKey -Key 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootInProgress' }
        { Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' }
        { Test-RegistryKey -Key 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending' }
        { Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting' }
        { Test-RegistryValueNotNull -Key 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations' }
        { Test-RegistryValueNotNull -Key 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations2' }
        { 
            # Ajout de test pour vérifier d'abord si la clé existe, l'utilisation de "ErrorAction ignore" retournera incorrectement $true
            'HKLM:\SOFTWARE\Microsoft\Updates' | Where-Object { test-path $_ -PathType Container } | ForEach-Object {            
                (Get-ItemProperty -Path $_ -Name 'UpdateExeVolatile' | Select-Object -ExpandProperty UpdateExeVolatile) -ne 0 
            }
        }
        { Test-RegistryValue -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Value 'DVDRebootSignal' }
        { Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\ServerManager\CurrentRebootAttemps' }
        { Test-RegistryValue -Key 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon' -Value 'JoinDomain' }
        { Test-RegistryValue -Key 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon' -Value 'AvoidSpnSet' }
        {
            # Ajout de test pour vérifier d'abord si les clés existent, sinon chaque groupe retournera $Null
            # Peut-être nécessaire d'évaluer ce que cela signifie si une ou les deux de ces clés n'existent pas
            ( 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName' | Where-Object { test-path $_ } | %{ (Get-ItemProperty -Path $_ ).ComputerName } ) -ne 
            ( 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName' | Where-Object { Test-Path $_ } | %{ (Get-ItemProperty -Path $_ ).ComputerName } )
        }
        {
            # Ajout de test pour vérifier d'abord si la clé existe
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object { 
                (Test-Path $_) -and (Get-ChildItem -Path $_) } | ForEach-Object { $true }
        }
    )

    foreach ($test in $tests) {
        Write-Verbose "Running scriptblock: [$($test.ToString())]"
        if (& $test) {
            $true
            break
        }
    }
}

foreach ($computer in $ComputerName) {
    try {
        $connParams = @{
            'ComputerName' = $computer
        }
        if ($PSBoundParameters.ContainsKey('Credential')) {
            $connParams.Credential = $Credential
        }

        $output = @{
            ComputerName    = $computer
            IsPendingReboot = $false
        }

        $psRemotingSession = New-PSSession @connParams
        
        if (-not ($output.IsPendingReboot = Invoke-Command -Session $psRemotingSession -ScriptBlock $scriptBlock)) {
            $output.IsPendingReboot = $false
        }
        [pscustomobject]$output
    } catch {
        Write-Error -Message $_.Exception.Message
    } finally {
        if (Get-Variable -Name 'psRemotingSession' -ErrorAction Ignore) {
            $psRemotingSession | Remove-PSSession
        }
    }
}

Vous pouvez maintenant l’exécuter comme ceci:

PS51> .\Test-PendingReboot.ps1 -Server SRV1,SRV2,SRV3,etc

Résumé

Vous devriez maintenant avoir un moyen rapide de tester le redémarrage en attente sur les serveurs Windows. Vous pouvez voir qu’en utilisant PowerShell, vous pouvez consolider de nombreuses étapes fastidieuses en un seul script. Ce script vous permet de tester rapidement un redémarrage en attente sur de nombreux serveurs à la fois.

Si vous connaissez d’autres indications à vérifier pour un redémarrage en attente, veuillez me le faire savoir.

Source:
https://adamtheautomator.com/pending-reboot-registry/