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에서 이와 같은 도구를 만드는 방법을 배우고 싶지 않다면, 저는 여러분을 위해 간단하게 만들었습니다. 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에서 pull request를 발행해 주세요.

이런 도구를 만드는 방법을 배우고 싶다면 계속 읽어보세요!

Pending Reboot PowerShell 도구 만들기

먼저, 테스트할 컴퓨터를 모두 정의해야 합니다. 이를 위한 여러 가지 방법이 있지만, 이 예제에서는 배열을 통해 수동으로 정의하겠습니다.

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

이제 foreach 루프를 생성하여 각각의 컴퓨터를 반복합니다.

foreach ($computer in $ComputerName)

}

다음으로, PowerShell Remoting을 사용하여 각 레지스트리 키와 값 조건을 단일 PSSession 내에서 확인하는 것을 추천합니다. 각 서버에 대해 PSSession을 생성하세요.

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

PSSession을 생성했다면, 이제 검사를 실행해야 합니다.

다음과 같은 여러 가지 검사를 동일한 코드를 사용하여 실행할 예정입니다:

  • 레지스트리 키가 존재하는지 테스트하기
  • 레지스트리 값이 존재하는지 테스트하기
  • 레지스트리 값이 null이 아닌지 테스트하기

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/