使用 PowerShell 在 Windows 伺服器上測試待重新啟動的系統

每當您安裝軟件、更新或進行配置更改時,Windows 需要重新啟動是很常見的。許多操作系統任務有時會強制 Windows 需要重新啟動。當重新啟動待定時,Windows 會添加一些註冊表值以顯示這一情況。在本博客文章中,您將學習如何檢查待定的重新啟動,以及如何構建一個 PowerShell 腳本來自動執行此任務。

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中構建像這樣的工具,我已經為你提供了一種簡單的方法。只需打開PowerShell控制台,輸入Install-Script Test-PendingRebootInstall-Script將從PowerShell庫下載我的PowerShell腳本到C:\Program Files\WindowsPowerShell\Scripts。然後按照下面的示例運行腳本。

PS51> Test-PendingReboot.ps1 -ComputerName localhost

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

你可以通過ComputerName參數提供任意多台服務器。腳本將返回TrueFalse以及服務器名稱。

這個工具將為你檢查上面表格中的所有註冊表鍵。

如果您想添加我遗漏的条件或纠正我犯的错误,请随时在GitHub上发起一个pull请求来修复它。

如果您想学习如何构建这样的工具,请继续阅读!

构建一个待处理重启的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/