בדיקת המתנה לאחר הפעלה מחדש על שרתי Windows באמצעות PowerShell

כל פעם שאתה מתקין תוכנה, עדכונים או עושה שינויים בהגדרות, זה די נפוץ של 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 Remoting מוגדרת וזמינה בשרתים שלך.

בדיקה אם יש הפעלה מחדש בתור (הדרך הקלה)

אם אינך רוצה ללמוד איך לבדוק את המפתחות ברישום אלו ולבנות כלי כזה בפוורשל, עשיתי זאת בשבילך. פשוט פתח את חלון הפוורשל שלך והקלד 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 כדי לתקן אותם.

אם ברצונך ללמוד איך לבנות כלי כזה, המשך לקרוא!

בניית כלי 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
        }
    }

בתוך אותו scriptblock, הגדירו כל תנאי בהפניה לפונקציות העזר שיצרתם לפני זמן קצר.

$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, ניתן לקonsolidate צעדים מייגעים רבים לתוך סקריפט אחד. סקריפט זה מאפשר לך לבדוק מהר עבור הפעלה ממתינה במקביל במספר שרתים.

אם יש לך ידיעה על אינדיקציות נוספות לבדיקה של הפעלה ממתינה, אני אשמח לדעת.

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