Summary: Guest blogger, Brian Wilhite, talks about using Windows PowerShell to detect a server that is in pending reboot status.
Microsoft Scripting Guy, Ed Wilson, is here. Today we have the conclusion to Brian Wilhite’s guest blog series about detecting pending reboots via Windows PowerShell. Prior to reading today’s post, you should read Determine Pending Reboot Status—PowerShell Style! Part 1. You can also check out Brian’s prior Hey, Scripting Guy! Blog posts.
The Windows PowerShell “awesomeness”
Now that I’m satisfied with my research and validation, I’m ready to write some Windows PowerShell “awesomeness.” Also, I think it’s important to determine exactly what my function will return before getting started. I decided on the following properties in a PSObject:
- Computer: Target computer (string)
- CBServicing: Representing the component-based servicing after Windows 2008 (Boolean)
- WindowsUpdate: Representing the WindowsUpdate\Auto Update Registry value (Boolean)
- CCMClientSDK: Representing the result of the Service Center Configuration Manager 2012 client WMI DetermineIfRebootPending method (Boolean)
- PendFileRename: Representing whether the PendingFileRenameOperations REG_MULTI_SZ entry has a value (Boolean)
- PendFileRenVal: Representing the value of the PendingFileRenameOperations REG_MULTI_SZ entry (string[])
- RebootPending: Representing a culmination of the Boolean values from the previous properties. If any value returns $true, so this property will be (Boolean)
With the object defined, I can commence developing the function. When authoring a function, I use a template that basically prepopulates the function's framework. I will be covering the core code components of the function, mainly the contents of the Process block. Although the template is a good tool, it's important to show how the ComputerName parameter is defined:
[CmdletBinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("CN","Computer")]
[String[]]$ComputerName="$env:COMPUTERNAME"
)
The previous parameter line has ValueFromPipeline and ValueFromPipelineByPropertyName defined for the ComputerName parameter.
- ValueFromPipleline enables strings to be piped into the function and processed via the process block.
- ValueFromPipelineByPropertyName enables the ComputerName property from other objects to be piped to the function and processed by the process block.
Because ComputerName was typed as a string array, I’m going to iterate through every computer in ComputerName and run the following code, explaining each step along the way.
First, I set several values to $false to reduce the number of if/else statements. Notice that I can use one equal sign (=) to assign multiple values to multiple variables on one line.
$PendFileRename,$Pending,$SCCM = $false,$false,$false
Next, I assign $null to the $CBSRebootPend variable because operating systems prior to Windows Server 2003 do not have Component-Based Servicing (CBS).
$CBSRebootPend = $null
This allows the CBServicing property, which the function returns, to be null if the Component-Based Servicing registry key does not exist. Next, I query for the BuildNumber via WMI to determine whether the Component-Based Servicing key needs to be queried.
$WMI_OS = Get-WmiObject -Class Win32_OperatingSystem -Property BuildNumber, CSName -ComputerName $Computer
Notice that I used the Property parameter for Get-WmiObject for optimization purposes. Performing the query in this way reduces the number of properties returned—therefore, decreasing the time it takes to run against multiple systems. Next, I make a connection to the registry by using the [Microsoft.Win32.RegistryKey] type accelerator.
$RegCon = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]"LocalMachine",$Computer)
If you review the Microsoft.Win32.RegistryKey class reference page on MSDN, you’ll notice an OpenRemoteBaseKey method that accepts two arguments, first the RegistryHive, and then a string that represents the remote computer. The following code is used to determine if the CBS key is present. If it is present, it executes the query, and performs it on computers with a BuildNumber of 6001 (post Windows Vista and Windows Server 2008 RTM versions).
If ($WMI_OS.BuildNumber -ge 6001)
{
The OpenSubKey method has a GetSubKeyNames method that is called and stored in the $RegSubKeysCBS variable.
$RegSubKeysCBS = $RegCon.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\").GetSubKeyNames()
The $CBSRebootPend variable will be $true if the RebootPending key is present in the $RegSubKeysCBS variable; it will be $false if it’s not. The $CBSRebootPend variable will be used later for the CBServicing property.
$CBSRebootPend = $RegSubKeysCBS -contains "RebootPending"
}## End If ($WMI_OS.BuildNumber -ge 6001)
Because the WindowsUpdate/Auto Update and the PendingFileRenameOperations registry values are common on all versions of Windows (at least since Windows XP and Windows Server 2003), I’ll query those entries unconditionally.
$RegWUAU = $RegCon.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\")
$RegWUAURebootReq = $RegWUAU.GetSubKeyNames()
The $WUAURebootReq variable will be $true if the RebootRequired key is present. Again, it will be $false if it’s not. This variable will also be used later for the WindowsUpdate property.
$WUAURebootReq = $RegWUAURebootReq -contains "RebootRequired"
$RegSubKeySM = $RegCon.OpenSubKey("SYSTEM\CurrentControlSet\Control\Session Manager\")
Here I’ve captured the value of the PendingFileRenameOperations entry. I’ll use the $RegValuePFRO variable to populate the PendFileRenVal property:
$RegValuePFRO = $RegSubKeySM.GetValue("PendingFileRenameOperations",$null)
The registry data collection is complete, so I’m going to clean up the connection object. Any time you us this method to connect to the registry, you should close the connection. Think of this as opening a door before entering a room—generally you close it when you leave. Working with the registry is really no different. So the following script will close your registry connection by calling the Close method from the $RegCon object that I created when I initially connected to the registry.
$RegCon.Close()
Now let’s evaluate the $RegValuePFRO variable to determine if the PendingFileRenameOperations REG_MULTI_SZ registry entry has a value. If it has a value, I’m going to set the $PendFileRename variable to $true, which will be used as the PendFileRename property.
If ($RegValuePFRO)
{
$PendFileRename = $true
}#End If ($RegValuePFRO)
Here, I’m setting the $CCMClientSDK to $null because there may be a chance that the function iterates through multiple systems and not allow the previous value to persist from another system:
$CCMClientSDK = $null
Next, I’m going to use a technique called splatting, when calling Invoke-WmiMethod for the DetermineIfRebootPending method.
Note Splatting is really cool. You can pass multiple parameters to a cmdlet or function by using a hash table. The “key” in the hash table will be the parameter name and the “value” will be the parameter’s argument.
$CCMSplat = @{
NameSpace='ROOT\ccm\ClientSDK'
Class='CCM_ClientUtilities'
Name='DetermineIfRebootPending'
ComputerName=$Computer
ErrorAction='SilentlyContinue'
}
To use the hash table that was just created, with all my Invoke-WmiMethod parameters, I’ll have to use the “@” character instead of referencing the “$” as you might assume.
$CCMClientSDK = Invoke-WmiMethod @CCMSplat
I like this technique because it allows me to clean up a one-liner in a script that may stretch across two screens. In the following image, check out how the splat technique is cleaner than the traditional one-liner.
OK, enough about splatting. Jumping back on track…
After the DetermineIfRebootPending WMI method is called, the following code is run to determine if the $SCCM value should be $true, $false, or $null. The $null value is returned if the CCM_ClientUtilities WMI class doesn’t exist. I have also incorporated error handling if the return code from the WMI method call is not equal to 0.
If ($CCMClientSDK)
{
If ($CCMClientSDK.ReturnValue -ne 0)
{
Write-Warning "Error: DetermineIfRebootPending returned error code $($CCMClientSDK.ReturnValue)"
}## End If ($CCMClientSDK -and $CCMClientSDK.ReturnValue -ne 0)
If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending)
{
$SCCM = $true
}## End If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending)
}## End If ($CCMClientSDK)
Else
{
$SCCM = $null
}
Next, if any of the $CBSRebootPend, $WUAURebootReq, $SCCM, or $PendFileRename variables are $true, I’ll set $Pending to $true, which will in turn populate the final property in my custom object, RebootPending.
If ($CBSRebootPend -or $WUAURebootReq -or $SCCM -or $PendFileRename)
{
$Pending = $true
}## End If ($CBS -or $WUAU -or $PendFileRename)
Finally, I create my custom object. Once again, I’ve used the splatting technique for my select statement. Because my PSObject was created by using a hash table, I will use the Select-Object cmdlet to order the properties based on how I think it is best presented to the user.
Note If you use Windows PowerShell 3.0, you can use the [PSCustomObject] type accelerator when you create an object and bypass the need to use the Select-Object technique.
$SelectSplat = @{
Property=('Computer','CBServicing','WindowsUpdate','CCMClientSDK','PendFileRename','PendFileRenVal','RebootPending')
}
New-Object -TypeName PSObject -Property @{
Computer=$WMI_OS.CSName
CBServicing=$CBSRebootPend
WindowsUpdate=$WUAURebootReq
CCMClientSDK=$SCCM
PendFileRename=$PendFileRename
PendFileRenVal=$RegValuePFRO
RebootPending=$Pending
} | Select-Object @SelectSplat
The following screenshot illustrates the output of my function when run without the ComputerName parameter, which defaults to $env:COMPUTERNAME.
You’ll notice that there are two values that are null. As noted earlier, this is by design because my local workstation does not have SCCM 2012 installed, nor was the PendingFileRenameOperations populated at the time I ran the function.
I’ve rambled enough for now. If you want to download my function, you can find it in the Script Center Repository:
Get-PendingReboot - Query Computer(s) For Pending Reboot State
If you have suggestions, questions, or concerns, please leave feedback on the download page of the Script Center Repository. Without the suggestions from others, this function wouldn’t be as complete as it is today. So if you have an idea, don’t hesitate to contact me. Until next time…
~Brian
Thank you, Brian. Awesome job. Join me tomorrow when I will talk about cool Windows PowerShell stuff.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy