Teste de Reinicialização Pendente em Servidores Windows com PowerShell

Sempre que você instala software, atualizações ou faz alterações de configuração, é comum o Windows precisar de reinicialização. Muitas vezes, tarefas do sistema operacional exigem que o Windows seja reiniciado. Quando uma reinicialização está pendente, o Windows adiciona alguns valores ao registro para indicar isso. Neste post do blog, você vai aprender como verificar se há uma reinicialização pendente e como criar um script PowerShell para automatizar a tarefa.

O Windows Precisa de Reinicialização

Quando você está no console, pode perceber que há uma reinicialização pendente por meio de uma caixa de diálogo ou notificação, conforme mostrado abaixo.

A partir dessa notificação, você pode reiniciar o Windows e pronto. Mas e se você não puder reiniciar imediatamente uma máquina quando for necessário? E se você acabou de instalar atualizações em um servidor de produção que não pode ser reiniciado agora?

Pending Windows reboot message

A reinicialização terá que esperar.

O tempo passa e, até lá, a reinicialização pode ser completamente esquecida! Quando você percebe, muitos servidores ou estações de trabalho precisam ser reiniciados, mas quais?

As Flags de Reinicialização Pendente estão no 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

Se você tiver o cliente do Microsoft System Center Configuration Manager (SCCM) instalado, também poderá ver esses métodos no 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

Depois de conhecer cada método para verificar uma reinicialização pendente, há muitas maneiras diferentes de verificar os valores do registro. Você pode abrir o regedit.exe e navegar manualmente por cada chave do registro.

Checking regedit manually

Verificar manualmente através do registro funciona, mas somos humanos. E se você esquecer de verificar um caminho do registro ou simplesmente esquecer quais verificar? Há uma maneira muito melhor de fazer isso. Você pode criar um script ou função para fazer isso por você. No meu caso, eu prefiro o PowerShell, então é isso que vou usar.

Usando um script do PowerShell, você pode consultar um ou todos os computadores em nosso domínio ou fornecer manualmente os nomes dos servidores para verificar se eles estão aguardando um reinício. Você pode então decidir se deseja reiniciá-los imediatamente ou fazer uma lista para reiniciar posteriormente. A escolha é sua.

Para usar o meu método do PowerShell, você precisará garantir que o PowerShell Remoting esteja configurado e disponível em seus servidores.

Testando se há um reinício pendente (A maneira fácil)

Se você não quer aprender como verificar essas chaves de registro e construir uma ferramenta como essa no PowerShell, eu facilitei para você. Basta abrir o console do PowerShell e digitar Install-Script Test-PendingReboot. Install-Script irá baixar o meu script do PowerShell Gallery para C:\Program Files\WindowsPowerShell\Scripts. Em seguida, execute o script como mostrado abaixo.

PS51> Test-PendingReboot.ps1 -ComputerName localhost

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

Você pode fornecer quantos servidores quiser através do parâmetro ComputerName. O script retornará True ou False juntamente com o nome do servidor.

Esta ferramenta verifica todas as chaves de registro na tabela acima para você.

Se desejar adicionar condições que eu tenha perdido ou corrigir quaisquer erros que eu tenha cometido, sinta-se à vontade para emitir uma solicitação de extração no GitHub para corrigi-lo.

Se você quiser aprender como construir uma ferramenta como esta, continue lendo!

Construindo uma Ferramenta PowerShell de Reinicialização Pendente

Primeiro, você precisará definir todos os computadores nos quais deseja testar uma reinicialização. Existem muitas maneiras diferentes de fazer isso, mas para esta demonstração, eu os definirei manualmente via um array.

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

Agora, crie um loop foreach para iterar sobre cada um deles.

foreach ($computer in $ComputerName)

}

Em seguida, recomendo o uso do PowerShell Remoting e verificar cada chave e valor do registro dentro de uma única PSSession. Crie uma PSSession para cada servidor.

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

Depois de ter uma PSSession criada, você precisará executar as verificações.

Como você estará executando muitas verificações diferentes usando o mesmo código, como por exemplo:

  • Testando se uma chave de registro existe
  • Testando se um valor de registro existe
  • Testando se um valor de registro não é 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 do mesmo bloco de script, defina cada condição fazendo referência às funções auxiliares que você acabou de criar.

$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' }
        { 
            # Adicionado teste para verificar primeiro se a chave existe, usando "ErrorAction ignore" retornará incorretamente $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' }
        {
            # Adicionado teste para verificar primeiro se as chaves existem, caso contrário, cada grupo retornará $Null
            # Pode ser necessário avaliar o que significa se uma ou ambas essas chaves não existirem
            ( '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 } )
        }
        {
            # Adicionado teste para verificar primeiro se a chave existe
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object { 
                (Test-Path $_) -and (Get-ChildItem -Path $_) } | ForEach-Object { $true }
        }
    )

Agora você pode criar um loop foreach dentro do seu $servers loop foreach que lê e executa cada teste.

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

Ao executar o código, o script retorna uma saída como esta:

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

Você pode criar essa saída garantindo que o loop foreach retorne um único objeto por servidor. Você deve saber que se qualquer dos valores do registro existir, então o servidor está pendente de reinicialização. Sabendo disso, você precisa retornar Verdadeiro se algum dos valores existir e Falso se nenhum deles existir.

Envolva tudo isso em um script e ele deve se parecer com isto (com algumas adições menores como Credencial).

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

    # Adicionado "test-path" a cada teste que não utilizava uma função personalizada acima desde
    # uma exceção é lançada quando Get-ItemProperty ou Get-ChildItem recebem um caminho de chave 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' }
        { 
            # Adicionado teste para verificar primeiro se a chave existe, usando "ErrorAction ignore" retornará incorretamente $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' }
        {
            # Adicionado teste para verificar primeiro se as chaves existem, se não cada grupo retornará $Null
            # Pode ser necessário avaliar o que significa se uma ou ambas essas chaves não existirem
            ( '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 } )
        }
        {
            # Adicionado teste para verificar primeiro se a chave 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
        }
    }
}

Agora você pode executá-lo assim:

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

Resumo

Agora você deve ter uma maneira rápida de testar reinicialização pendente em servidores Windows. Você pode ver que usando o PowerShell, você pode consolidar muitas etapas tediosas em um único script. Este script permite que você teste rapidamente se há uma reinicialização pendente em muitos servidores de uma vez.

Se você souber de outras indicações para verificar uma reinicialização pendente, por favor me avise.

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