Test in afwachting van herstart op Windows-servers met PowerShell

Telkens wanneer u software installeert, updates uitvoert of configuratiewijzigingen aanbrengt, is het gebruikelijk dat Windows een herstart vereist. Veel taken in het besturingssysteem kunnen ervoor zorgen dat Windows opnieuw moet worden opgestart. Wanneer er een herstart vereist is, voegt Windows enkele registerwaarden toe om dit aan te geven. In deze blogpost leert u hoe u kunt controleren of er een herstart vereist is en hoe u een PowerShell-script kunt maken om deze taak te automatiseren.

Windows heeft een herstart nodig

Wanneer u zich op de console bevindt, kunt u zien dat er een herstart vereist is aan de hand van een pop-upvenster of melding zoals hieronder weergegeven.

Vanuit die melding kunt u Windows opnieuw opstarten en klaar is Kees. Maar wat als u de machine niet onmiddellijk kunt herstarten wanneer dat nodig is? Wat als u net updates hebt geïnstalleerd op een productieserver en die server kan nu niet worden herstart?

Pending Windows reboot message

De herstart moet wachten.

De tijd verstrijkt en tegen die tijd kan de herstart volledig vergeten zijn! Op het moment dat u zich realiseert dat veel servers of werkstations moeten worden herstart, vraagt u zich af welke servers dat precies zijn?

Er zijn herstartvlaggen in het register

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

Als u de Microsoft System Center Configuration Manager (SCCM) client hebt geïnstalleerd, kunt u deze methoden ook in WMI zien.

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

Zodra u elke methode kent om te controleren of er een herstart vereist is, zijn er verschillende manieren om registerwaarden te controleren. U kunt bijvoorbeeld regedit.exe openen en handmatig door elke registerwaarde navigeren.

Checking regedit manually

Handmatig controleren via het register werkt, maar we zijn mensen. Wat als je vergeet om een registerpad te controleren of gewoon vergeet welke je moet controleren? Er is een veel betere manier om dit te doen. Je kunt een script of functie maken om dit voor jou te doen. In mijn geval geef ik de voorkeur aan PowerShell, dus dat is wat ik zal gebruiken.

Door een PowerShell-script te gebruiken, kun je één of alle computers in ons domein bevragen of handmatig de servernamen opgeven om te zien of ze een herstart nodig hebben. Je kunt dan beslissen of je ze meteen wilt herstarten of een lijst wilt maken om later te herstarten. De keuze is aan jou.

Om mijn PowerShell-methode te gebruiken, moet je ervoor zorgen dat PowerShell Remoting is ingesteld en beschikbaar is op je servers.

Testen op een uitgestelde herstart (de makkelijke manier)

Als je niet wilt leren hoe je deze registervermeldingen moet controleren en een tool zoals deze moet bouwen in PowerShell, heb ik het makkelijk voor je gemaakt. Open gewoon je PowerShell-console en typ Install-Script Test-PendingReboot. Install-Script zal mijn PowerShell-script downloaden van de PowerShell Gallery naar C:\Program Files\WindowsPowerShell\Scripts. Voer vervolgens het script uit zoals hieronder getoond.

PS51> Test-PendingReboot.ps1 -ComputerName localhost

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

Je kunt zoveel servers opgeven als je wilt via de parameter ComputerName. Het script geeft True of False terug samen met de servernaam.

Deze tool controleert alle registervermeldingen in de bovenstaande tabel voor jou.

Als je voorwaarden wilt toevoegen die ik over het hoofd heb gezien of eventuele fouten wilt corrigeren, voel je vrij om een pullverzoek op GitHub in te dienen om het te repareren.

Als je wilt leren hoe je een tool als deze kunt bouwen, lees dan verder!

Het bouwen van een PowerShell-tool voor het controleren van uitgestelde herstarts

Ten eerste moet je alle computers definiëren waarop je een herstart wilt testen. Er zijn veel verschillende manieren om dit te doen, maar voor deze demonstratie zal ik ze handmatig definiëren via een array.

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

Maak nu een foreach-lus om over elk van hen te itereren.

foreach ($computer in $ComputerName)

}

Vervolgens raad ik aan PowerShell Remoting te gebruiken en elke registersleutel- en waardeconditie te controleren binnen een enkele PSSession. Maak een PSSession voor elke server.

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

Zodra je een PSSession hebt gemaakt, moet je de controles uitvoeren.

Aangezien je veel verschillende controles zult uitvoeren met dezelfde code, zoals:

  • Testen of een registersleutel bestaat
  • Testen of een registerwaarde bestaat
  • Testen of een registerwaarde niet leeg is

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

Binnen dezelfde scriptblock, definieer elke voorwaarde door te verwijzen naar de hulpfuncties die je zojuist hebt gemaakt.

$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' }
        { 
            # Toegevoegde test om eerst te controleren of de sleutel bestaat, het gebruik van "ErrorAction ignore" zal onterecht $true retourneren
            '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' }
        {
            # Toegevoegde test om eerst te controleren of de sleutels bestaan, als niet elke groep zal $Null retourneren
            # Mogelijk moet worden geëvalueerd wat het betekent als een of beide van deze sleutels niet bestaan
            ( '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 } )
        }
        {
            # Toegevoegde test om eerst te controleren of de sleutel bestaat
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object { 
                (Test-Path $_) -and (Get-ChildItem -Path $_) } | ForEach-Object { $true }
        }
    )

Je kunt nu een foreach loop maken binnen je $servers foreach loop die elke test leest en elke test uitvoert.

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

Wanneer je de code uitvoert, retourneert het script een uitvoer zoals dit:

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

Je kunt deze uitvoer creëren door ervoor te zorgen dat de foreach loop een enkel object per server retourneert. Je moet weten dat als een van de registerwaarden bestaat, dan de server wacht op een herstart. Wanneer je dit weet, moet je True retourneren als een van de waarden bestaat en False als geen van hen bestaat.

Verpak dit alles in een script en het zou er zo uit moeten zien (met enkele kleine toevoegingen zoals 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" is toegevoegd aan elke test die geen aangepaste functie van hierboven gebruikte sinds
    # er een uitzondering wordt gegenereerd wanneer Get-ItemProperty of Get-ChildItem een niet-bestaand sleutelpad krijgt
    $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' }
        { 
            # Test toegevoegd om eerst te controleren of de sleutel bestaat, het gebruik van "ErrorAction ignore" zal onterecht $true retourneren
            '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' }
        {
            # Test toegevoegd om eerst te controleren of sleutels bestaan, als dat niet het geval is, zal elke groep $Null retourneren
            # Misschien moeten we evalueren wat het betekent als een van beide sleutels niet bestaat
            ( '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 } )
        }
        {
            # Test toegevoegd om eerst te controleren of de sleutel bestaat
            '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
        }
    }
}

Je kunt het nu zo uitvoeren:

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

Samenvatting

Je zou nu een snelle manier moeten hebben om een uitgestelde herstart over Windows-servers te testen. Je kunt zien dat door PowerShell te gebruiken, je veel vervelende stappen kunt samenvoegen tot één script. Dit script stelt je in staat om snel te controleren op een uitgestelde herstart over veel servers tegelijk.

Als je andere indicaties kent om te controleren op een uitgestelde herstart, laat het me dan weten.

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