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

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

O Windows Precisa de Reinicialização

Quando você está no console, pode notar que uma reinicialização está pendente por meio de uma caixa de diálogo pop-up ou notificação, como mostrado abaixo.

A partir dessa notificação, você pode reiniciar o Windows e finalizar. 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 precisa 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 bandeiras 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 se há uma reinicialização pendente, existem muitas maneiras diferentes de verificar os valores do registro. Você poderia abrir o regedit.exe e percorrer manualmente cada chave de registro.

Checking regedit manually

Manualmente, verificar através do registro funciona, mas somos humanos. E se você esquecer de verificar um caminho no 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.

Ao usar um script do PowerShell, você pode consultar um ou todos os computadores em nosso domínio ou fornecer manualmente os nomes dos servidores para ver se eles estão aguardando uma reinicialização. Você pode então decidir se reinicia-los imediatamente ou criar uma lista para reiniciar mais tarde. A escolha é sua.

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

Testando se há uma Reinicialização Pendente (O jeito fácil)

Se você não quiser aprender a verificar essas chaves de registro e construir uma ferramenta como essa no PowerShell, eu facilitei para você. Basta abrir seu console do PowerShell e digitar Install-Script Test-PendingReboot. O Install-Script irá baixar meu script do PowerShell da PowerShell Gallery para C:\Program Files\WindowsPowerShell\Scripts. Em seguida, execute o script conforme 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 junto 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 esquecido ou corrigir quaisquer erros que eu tenha cometido, sinta-se à vontade para enviar uma solicitação de extração no GitHub para corrigi-lo.

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

Construindo uma Ferramenta PowerShell de Reboot Pendente

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

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

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

foreach ($computer in $ComputerName)

}

Em seguida, recomendo usar o PowerShell Remoting e verificar cada chave de registro e condição de valor 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, tais como:

  • 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 scriptblock, defina cada condição referenciando as 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, 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 }
        }
    )

Agora você pode criar um loop foreach dentro do seu loop $servers foreach que lê cada teste 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 esta 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á aguardando uma reinicialização. Sabendo disso, você precisa retornar Verdadeiro se algum dos valores existir e Falso se nenhum deles existir.

Envolver tudo isso em um script e deve se parecer com isso (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, pois
    # 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, pois o uso de "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, 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 }
        }
    )

    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ções pendentes em servidores Windows. Você pode ver que, usando o PowerShell, é possível consolidar muitas etapas tediosas em um único script. Este script permite que você teste rapidamente se há uma reinicialização pendente em vários servidores de uma vez.

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

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