Test in sospeso su server Windows con PowerShell

Quando installi software, aggiornamenti o fai modifiche di configurazione, è comune che Windows necessiti di un riavvio. Molti compiti del sistema operativo talvolta costringono Windows a richiedere un riavvio. Quando è in attesa di un riavvio, Windows aggiunge alcuni valori del registro per mostrarlo. In questo post del blog, imparerai come controllare se c’è un riavvio in sospeso e come costruire uno script PowerShell per automatizzare il compito.

Windows ha bisogno di un riavvio

Quando sei nella console, puoi notare che un riavvio è in sospeso da una finestra di dialogo o una notifica come mostrato di seguito.

Da quella notifica, puoi riavviare Windows e finirla qui. Ma, cosa succede se non puoi riavviare immediatamente una macchina quando ne ha bisogno? E se hai appena installato aggiornamenti su un server di produzione e quel server non può essere riavviato ora?

Pending Windows reboot message

Il riavvio deve attendere.

Il tempo passa e quindi il riavvio potrebbe essere dimenticato del tutto! Quando ti rendi conto, molti server o postazioni di lavoro devono essere riavviati, ma quali?

Le bandiere di riavvio in sospeso sono nel Registro

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

Se hai il client Microsoft System Center Configuration Manager (SCCM) installato, potresti vedere anche questi metodi in 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

Una volta che conosci ciascun metodo per controllare se c’è un riavvio in sospeso, ci sono molti modi diversi per controllare i valori del registro. Potresti aprire regedit.exe e passare manualmente attraverso ogni chiave di registro.

Checking regedit manually

Il controllo manuale tramite il registro funziona, ma siamo umani. Cosa succede se dimentichi di controllare un percorso nel registro o semplicemente dimentichi quali controllare? C’è un modo molto migliore per farlo. Puoi creare uno script o una funzione per farlo al tuo posto. Nel mio caso, preferisco PowerShell, ed è quello che userò.

Utilizzando uno script PowerShell, puoi interrogare uno o tutti i computer nel nostro dominio o fornire manualmente i nomi dei server per vedere se sono in attesa di un riavvio. Puoi quindi decidere se riavviarli subito o creare una lista per riavviarli in seguito. La scelta è tua.

Per utilizzare il mio metodo PowerShell, è necessario assicurarsi che PowerShell Remoting sia configurato e disponibile sui tuoi server.

Test per un Riavvio in Sospeso (Il Modo Semplice)

Se non vuoi imparare come verificare queste chiavi di registro e costruire uno strumento simile in PowerShell, te lo rendo facile. Apri semplicemente la console PowerShell e digita Install-Script Test-PendingReboot. Install-Script scaricherà il mio script PowerShell dalla PowerShell Gallery in C:\Program Files\WindowsPowerShell\Scripts. Quindi esegui lo script come mostrato di seguito.

PS51> Test-PendingReboot.ps1 -ComputerName localhost

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

Puoi fornire tanti server quanti ne vuoi tramite il parametro ComputerName. Lo script restituirà True o False insieme al nome del server.

Questo strumento controlla tutte le chiavi di registro nella tabella sopra per te.

Se desideri aggiungere condizioni che ho trascurato o correggere eventuali errori che ho commesso, sentiti libero di emettere una richiesta di pull su GitHub per correggerlo.

Se vuoi imparare come costruire uno strumento come questo, continua a leggere!

Costruzione di uno Strumento PowerShell per Riavvii in Sospeso

Innanzitutto, dovrai definire tutti i computer su cui desideri testare un riavvio. Ci sono molti modi diversi per farlo, ma per questa dimostrazione, li definirò manualmente tramite un array.

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

Ora crea un ciclo foreach per iterare su ciascuno di essi.

foreach ($computer in $ComputerName)

}

Successivamente, ti consiglio di utilizzare il Remoting di PowerShell e controllare ogni chiave di registro e condizione di valore all’interno di una singola PSSession. Crea una PSSession per ogni server.

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

Una volta creata una PSSession, dovrai quindi eseguire i controlli.

Dato che eseguirai molti controlli diversi utilizzando lo stesso codice, come ad esempio:

  • Verifica se esiste una chiave di registro
  • Verifica se esiste un valore di registro
  • Verifica se un valore di registro non è nullo

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

All’interno dello stesso scriptblock, definisci ogni condizione facendo riferimento alle funzioni di supporto che hai appena creato.

$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' }
        { 
            # Aggiunto test per controllare prima se la chiave esiste, utilizzando "ErrorAction ignore" restituirà erroneamente $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' }
        {
            # Aggiunto test per controllare prima se le chiavi esistono, se non esistono restituirà $Null per ciascun gruppo
            # Potrebbe essere necessario valutare cosa significa se una o entrambe queste chiavi non esistono
            ( '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 } )
        }
        {
            # Aggiunto test per controllare prima se la chiave esiste
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object { 
                (Test-Path $_) -and (Get-ChildItem -Path $_) } | ForEach-Object { $true }
        }
    )

Ora puoi creare un ciclo foreach all’interno del tuo ciclo $servers foreach che legge ed esegue ciascun test.

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

Quando esegui il codice, lo script restituisce un output come questo:

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

Puoi creare questo output assicurandoti che il ciclo foreach restituisca un singolo oggetto per server. Dovresti sapere che se qualunque dei valori del registro esiste, allora il server è in attesa di riavvio. Sapendo questo, devi restituire True se uno qualsiasi dei valori esiste e False se nessuno di essi esiste.

Avvolgi tutto questo in uno script e dovrebbe assomigliare a questo (con alcune aggiunte minori come 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
        }
    }

    # Aggiunto "test-path" a ciascun test che non sfruttava una funzione personalizzata dall'alto poiché
    # viene generata un'eccezione quando Get-ItemProperty o Get-ChildItem vengono passati un percorso di chiave inesistente
    $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' }
        { 
            # Aggiunto test per verificare prima se la chiave esiste, utilizzando "ErrorAction ignore" restituirà erroneamente $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' }
        {
            # Aggiunto test per verificare prima se le chiavi esistono, se non esistono ciascun gruppo restituirà $Null
            # Potrebbe essere necessario valutare cosa significa se una o entrambe queste chiavi non esistono
            ( '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 } )
        }
        {
            # Aggiunto test per verificare prima se la chiave esiste
            '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
        }
    }
}

Ora puoi eseguirlo in questo modo:

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

Riassunto

Ora dovresti avere un modo rapido per testare il riavvio in sospeso su server Windows. Puoi vedere che utilizzando PowerShell, puoi consolidare molti passaggi noiosi in uno script. Questo script ti consente di testare rapidamente un riavvio in sospeso su molti server contemporaneamente.

Se conosci altri indicatori da verificare per un riavvio in sospeso, per favore fammelo sapere.

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