Prueba pendiente de reinicio en servidores Windows con PowerShell

Cada vez que instalas software, actualizaciones o realizas cambios de configuración, es común que Windows necesite reiniciarse. Muchas tareas del sistema operativo obligan a Windows a requerir un reinicio. Cuando hay un reinicio pendiente, Windows agrega algunos valores de registro para mostrarlo. En esta publicación del blog, aprenderás cómo verificar si hay un reinicio pendiente y cómo crear un script de PowerShell para automatizar la tarea.

Windows necesita reiniciarse

Cuando estás en la consola, puedes darte cuenta de que hay un reinicio pendiente por una ventana emergente o una notificación como se muestra a continuación.

A partir de esa notificación, puedes reiniciar Windows y ya está. Pero, ¿qué pasa si no puedes reiniciar de inmediato una máquina cuando es necesario? ¿Qué pasa si acabas de instalar actualizaciones en un servidor de producción y ese servidor no se puede reiniciar en este momento?

Pending Windows reboot message

El reinicio debe esperar.

El tiempo pasa y para entonces el reinicio puede ser completamente olvidado. Cuando te das cuenta, muchos servidores o estaciones de trabajo necesitan reiniciarse, pero ¿cuáles?

Las banderas de reinicio pendiente están en el Registro

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 tienes instalado el cliente Microsoft System Center Configuration Manager (SCCM), también puedes ver estos métodos en 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

Una vez que conoces cada método para verificar un reinicio pendiente, hay muchas formas diferentes de verificar los valores del registro. Podrías abrir regedit.exe y buscar manualmente en cada clave de registro.

Checking regedit manually

Comprobando manualmente a través del registro funciona, pero somos humanos. ¿Y si olvidas revisar una ruta del registro o simplemente olvidas cuáles revisar? Hay una manera mucho mejor de hacer esto. Puedes crear un script o función para que lo haga por ti. En mi caso, prefiero PowerShell, así que eso es lo que usaré.

Al usar un script de PowerShell, puedes consultar una o todas las computadoras en nuestro dominio o proporcionar manualmente los nombres de los servidores para ver si están pendientes de reinicio. Luego puedes decidir si reiniciarlos de inmediato o hacer una lista para reiniciar más tarde. La elección es tuya.

Para usar mi método de PowerShell, debes asegurarte de que PowerShell Remoting esté configurado y disponible en tus servidores.

Pruebas para un reinicio pendiente (La manera fácil)

Si no quieres aprender a verificar estas claves del registro y construir una herramienta como esta en PowerShell, te lo he facilitado. Simplemente abre tu consola de PowerShell y escribe Install-Script Test-PendingReboot. Install-Script descargará mi script de PowerShell desde la PowerShell Gallery a C:\Program Files\WindowsPowerShell\Scripts. Luego ejecuta el script como se muestra a continuación.

PS51> Test-PendingReboot.ps1 -ComputerName localhost

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

Puedes proporcionar tantos servidores como desees mediante el parámetro ComputerName. El script devolverá True o False junto con el nombre del servidor.

Esta herramienta verifica todas las claves del registro en la tabla anterior por ti.

Si deseas agregar condiciones que haya omitido o corregir errores que haya cometido, siéntete libre de enviar una solicitud de extracción en GitHub para corregirlo.

Si quieres aprender cómo construir una herramienta como esta, ¡sigue leyendo!

Construyendo una Herramienta de PowerShell para Reinicio Pendiente

Primero, necesitarás definir todas las computadoras en las que te gustaría probar un reinicio. Hay muchas formas diferentes de hacerlo, pero para esta demostración, las definiré manualmente mediante un array.

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

Ahora crea un bucle foreach para iterar sobre cada una de ellas.

foreach ($computer in $ComputerName)

}

A continuación, recomiendo usar PowerShell Remoting y verificar cada clave y valor de registro dentro de una única PSSession. Crea una PSSession para cada servidor.

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

Una vez que hayas creado una PSSession, entonces necesitarás ejecutar las verificaciones.

Dado que estarás ejecutando muchas verificaciones diferentes usando el mismo código, como:

  • Pruebas si existe una clave de registro
  • Pruebas si existe un valor de registro
  • Pruebas si un valor de registro no es nulo

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
        }
    }

Dentro de ese mismo scriptblock, define cada condición haciendo referencia a las funciones auxiliares que acabas de crear.

$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' }
        { 
            # Se agregó una prueba para verificar primero si la clave existe, usando "ErrorAction ignore" devolverá incorrectamente $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' }
        {
            # Se agregó una prueba para verificar primero si las claves existen, si no, cada grupo devolverá $Null
            # Es posible que necesite evaluar qué significa si una o ambas claves no existen
            ( '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 } )
        }
        {
            # Se agregó una prueba para verificar primero si la clave existe
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object { 
                (Test-Path $_) -and (Get-ChildItem -Path $_) } | ForEach-Object { $true }
        }
    )

Ahora puedes crear un bucle foreach dentro de tu bucle $servers foreach que lea cada prueba y ejecute cada prueba.

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

Cuando ejecutas el código, el script devuelve una salida como esta:

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

Puedes crear esta salida asegurándote de que el bucle foreach devuelva un único objeto por servidor. Debes saber que si alguno de los valores del registro existe, entonces el servidor está pendiente de reinicio. Sabiendo esto, entonces necesitas devolver True si alguno de los valores existe y False si ninguno de ellos existe.

Envuelve todo esto en un script y debería lucir así (con algunas adiciones menores como 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
        }
    }

    # Se agregó "test-path" a cada prueba que no utilizaba una función personalizada de arriba, ya que
    # se genera una excepción cuando Get-ItemProperty o Get-ChildItem reciben una ruta de clave inexistente
    $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' }
        { 
            # Se agregó una prueba para verificar primero si la clave existe, ya que "ErrorAction ignore" devolverá incorrectamente $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' }
        {
            # Se agregó una prueba para verificar primero si las claves existen, si no, cada grupo devolverá $Null
            # Puede ser necesario evaluar qué significa si una o ambas de estas claves no existen
            ( '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 } )
        }
        {
            # Se agregó una prueba para verificar primero si la clave 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
        }
    }
}

Ahora puedes ejecutarlo de esta manera:

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

Resumen

Ahora deberías tener una forma rápida de probar si hay un reinicio pendiente en los servidores Windows. Puedes ver que al usar PowerShell, puedes consolidar muchas tareas tediosas en un solo script. Este script te permite probar rápidamente si hay un reinicio pendiente en muchos servidores a la vez.

Si conoces alguna otra indicación para verificar si hay un reinicio pendiente, por favor házmelo saber.

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