Тестирование ожидания перезагрузки на серверах Windows с помощью PowerShell

Всякий раз, когда вы устанавливаете программное обеспечение, обновления или вносите изменения в настройки, часто требуется перезагрузка системы Windows. Некоторые задачи операционной системы иногда заставляют Windows требовать перезагрузку. Когда перезагрузка необходима, Windows добавляет некоторые значения реестра для отображения этого. В этом блоге вы узнаете, как проверить наличие ожидающей перезагрузки и как создать сценарий PowerShell для автоматизации задачи.

Windows требует перезагрузки

Когда вы находитесь в консоли, вы можете заметить, что перезагрузка ожидается появлением всплывающего окна или уведомления, как показано ниже.

Из этого уведомления вы можете перезагрузить Windows и закончить с этим. Но что, если вы не можете немедленно перезагрузить компьютер, когда это требуется? Что, если вы только что установили обновления на рабочий сервер, который нельзя перезагрузить прямо сейчас?

Pending Windows reboot message

Перезагрузка должна подождать.

Время идет, и к тому времени перезагрузка может быть полностью забыта! К тому моменту, когда вы понимаете это, многие серверы или рабочие станции нуждаются в перезагрузке, но какие?

Флаги ожидания перезагрузки находятся в реестре

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

Если у вас установлен клиент Microsoft System Center Configuration Manager (SCCM), вы также можете увидеть эти методы в 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

Когда вы узнаете каждый метод проверки наличия ожидающей перезагрузки, существует множество различных способов проверки значений реестра. Вы можете открыть regedit.exe и вручную просмотреть каждый ключ реестра.

Checking regedit manually

Manually checking via the registry works but we’re human. What if you forget to check one registry path or just forget which ones to check? There’s a much better way to do this. You can create a script or function to do this for you. In my case, I prefer PowerShell so that’s what I’ll use.

Используя сценарий PowerShell, вы можете запросить один или все компьютеры в нашем домене или вручную указать имена серверов, чтобы узнать, ожидает ли их перезагрузка. Затем вы можете принять решение о том, перезагрузить их сейчас или создать список для перезагрузки позже. Выбор за вами.

Для использования моего метода PowerShell, убедитесь, что PowerShell Remoting настроен и доступен на ваших серверах.

Тестирование на предмет ожидаемой перезагрузки (простой способ)

Если вы не хотите учиться проверять эти ключи реестра и создавать такой инструмент на PowerShell, я упростил для вас. Просто откройте консоль PowerShell и введите Install-Script Test-PendingReboot. Install-Script загрузит мой сценарий PowerShell из Галереи PowerShell в C:\Program Files\WindowsPowerShell\Scripts. Затем запустите сценарий, как показано ниже.

PS51> Test-PendingReboot.ps1 -ComputerName localhost

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

Вы можете указать столько серверов, сколько хотите, с помощью параметра ComputerName. Сценарий вернет True или False, а также имя сервера.

Этот инструмент проверяет все ключи реестра из таблицы выше за вас.

Если вы хотите добавить условия, которые я упустил, или исправить ошибки, которые я сделал, не стесняйтесь отправить запрос на внесение изменений на GitHub, чтобы исправить это.

Если вы хотите узнать, как создать такой инструмент, читайте дальше!

Создание инструмента PowerShell для проверки ожидающей перезагрузки

Сначала вам нужно определить все компьютеры, на которых вы хотите протестировать перезагрузку. Есть много различных способов сделать это, но для этой демонстрации я определю их вручную через массив.

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

Теперь создайте цикл foreach, чтобы перебрать каждый из них.

foreach ($computer in $ComputerName)

}

Затем я рекомендую использовать удаленное управление PowerShell и проверять каждый ключ реестра и условие значения внутри одной PSSession. Создайте PSSession для каждого сервера.

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

После создания PSSession вам нужно будет запустить проверки.

Поскольку вы будете выполнять множество различных проверок с использованием одного и того же кода, таких как:

  • Тестирование наличия ключа реестра
  • Тестирование наличия значения реестра
  • Тестирование того, что значение реестра не является пустым

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

Внутри этого же скриптового блока определите каждое условие, ссылаясь на вспомогательные функции, которые вы только что создали.

$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' }
        { 
            # Добавлен тест для проверки сначала наличия ключа; использование "ErrorAction ignore" неверно возвращает $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' }
        {
            # Добавлен тест для проверки сначала наличия ключей; если их нет, каждая группа вернет $Null
            # Может потребоваться оценить, что означает отсутствие одного или обоих этих ключей
            ( '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 } )
        }
        {
            # Добавлен тест для проверки сначала наличия ключа
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object { 
                (Test-Path $_) -and (Get-ChildItem -Path $_) } | ForEach-Object { $true }
        }
    )

Теперь вы можете создать цикл foreach внутри вашего цикла $servers foreach, который читает каждый тест и выполняет его.

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

При запуске кода сценарий возвращает вывод, подобный этому:

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

Вы можете создать этот вывод, убедившись, что цикл foreach возвращает один объект на сервер. Вы должны знать, что если существует хотя бы одно значение реестра, то сервер ожидает перезагрузки. Зная это, вам нужно вернуть True, если хотя бы одно из значений существует, и False, если ни одно из них не существует.

Упакуйте все это в сценарий, и он должен выглядеть примерно так (с некоторыми небольшими добавлениями, такими как 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
        }
    }

    # Добавлен "test-path" к каждому тесту, который не использовал пользовательскую функцию выше, так как
    # выбрасывается исключение, когда Get-ItemProperty или Get-ChildItem передаются несуществующий ключевой путь
    $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' }
        { 
            # Добавлен тест для проверки сначала наличия ключа, использование "ErrorAction ignore" неправильно вернет $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' }
        {
            # Добавлен тест для проверки сначала наличия ключей, если их нет, каждая группа вернет $Null
            # Может потребоваться оценить, что означает отсутствие одного или обоих этих ключей
            ( '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 } )
        }
        {
            # Добавлен тест для проверки сначала наличия ключа
            '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
        }
    }
}

Теперь вы можете выполнить его так:

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

Краткое описание

Теперь у вас есть быстрый способ проверить ожидание перезагрузки на серверах Windows. Вы можете видеть, что, используя PowerShell, вы можете объединить множество утомительных шагов в один сценарий. Этот сценарий позволяет быстро проверить наличие ожидания перезагрузки на многих серверах одновременно.

Если у вас есть еще какие-либо признаки, которые нужно проверить для ожидания перезагрузки, пожалуйста, дайте мне знать.

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