使用PowerShell在Windows服务器上进行测试挂起重新启动

每当您安装软件、更新或进行配置更改时,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

手动检查注册表可以,但我们是人类。如果您忘记检查一个注册表路径,或者只是忘记了要检查哪些呢?有一个更好的方法可以做到这一点。您可以创建一个脚本或函数来帮助您完成这项任务。在我的情况下,我更喜欢使用 PowerShell,所以我会使用它。

通过使用 PowerShell 脚本,您可以查询我们域中的一个或所有计算机,或手动提供服务器名称,以查看它们是否需要重新启动。然后,您可以决定是立即重新启动它们,还是制作一个重新启动的列表以供稍后使用。选择权在您手中。

要使用我的 PowerShell 方法,您需要确保 PowerShell 远程已设置并在您的服务器上可用。

测试挂起重新启动(简单方法)

如果您不想学习如何检查这些注册表键并在 PowerShell 中构建类似工具,我已经为您简化了。只需打开 PowerShell 控制台,然后键入 Install-Script Test-PendingRebootInstall-Script 将从 PowerShell Gallery 下载我的 PowerShell 脚本到 C:\Program Files\WindowsPowerShell\Scripts。然后按下面所示运行该脚本。

PS51> Test-PendingReboot.ps1 -ComputerName localhost

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

您可以通过 ComputerName 参数提供尽可能多的服务器。该脚本将返回 TrueFalse 以及服务器名称。

此工具将为您检查上表中的所有注册表键。

如果您想添加我可能遗漏的条件或更正我可能犯的任何错误,请随时发出一个 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 }
        }
    )

现在,您可以在$serversforeach循环内创建另一个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/