Skip to content

Instantly share code, notes, and snippets.

@ned1313
Created February 6, 2017 21:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save ned1313/df1564dbd2c4e89757103772d48403c6 to your computer and use it in GitHub Desktop.
Save ned1313/df1564dbd2c4e89757103772d48403c6 to your computer and use it in GitHub Desktop.
<#
WindowsUpdatePowerShell script
Written by Ned Bellavance with functions borrowed from Brian White and the
WindowsUpdate module written by Michal Gajda. This script is meant to automate
the installation of Windows Updates on a brand new system. This is not a
replacement for WSUS or SCCM or anything centrally managed. It installs all
available, required Windows Updates until there are none left. It will
require an internet connection, of course, and local Administrator credentials.
The script is run without parameters. Logs are written out to the
folder containing the script.
The script will require several reboots as each successive list of updates is
processed and installed. When all updates have been installed, the script will
stop running and clean up the scheduled task.
This script is provided with no guarantees. Your mileage may vary. Etc, etc.
#>
param(
[string] $scriptPath = $PSScriptRoot
)
#Create Log file for the run
$logFile = "$scriptPath\WindowsUpdatePowerShell_LogFile_$(Get-Date -f yyyy-MM-dd-hh-mm-ss).txt"
Add-Content $logFile -value ("$(get-date -f s) Log File Started") -PassThru | Write-Output
Add-Content $logFile -value ("$(get-date -f s) Running $PSCommandPath in $scriptPath") | Write-Output
Add-Content $logFile -value ("$(get-date -f s) Check for scheduled task") | Write-Output
#Check for scheduled Task
$task = Get-ScheduledTask -TaskName "WindowsUpdatePowerShell" -ErrorAction SilentlyContinue
#Create a scheduled task if there is none
if(-not $task){
Add-Content $logFile -value ("$(get-date -f s) Creating scheduled task") -PassThru | Write-Output
$trigger = New-ScheduledTaskTrigger -AtStartup -RandomDelay "00:01:00"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NonInteractive -NoProfile -File $PSCommandPath" -WorkingDirectory $scriptPath
$settings = New-ScheduledTaskSettingsSet
$task = New-ScheduledTask -Action $action -Description "WindowsUpdatePowerShell" -Settings $settings -Trigger $trigger
Register-ScheduledTask WindowsUpdatePowerShell -InputObject $task -User System
}
Add-Content $logFile -value ("$(get-date -f s) Load Pending Reboot Function") -PassThru | Write-Output
Function Get-PendingReboot
{
<#
.SYNOPSIS
Gets the pending reboot status on a local or remote computer.
.DESCRIPTION
This function will query the registry on a local or remote computer and determine if the
system is pending a reboot, from Microsoft updates, Configuration Manager Client SDK, Pending Computer
Rename, Domain Join or Pending File Rename Operations. For Windows 2008+ the function will query the
CBS registry key as another factor in determining pending reboot state. "PendingFileRenameOperations"
and "Auto Update\RebootRequired" are observed as being consistant across Windows Server 2003 & 2008.
CBServicing = Component Based Servicing (Windows 2008+)
WindowsUpdate = Windows Update / Auto Update (Windows 2003+)
CCMClientSDK = SCCM 2012 Clients only (DetermineIfRebootPending method) otherwise $null value
PendComputerRename = Detects either a computer rename or domain join operation (Windows 2003+)
PendFileRename = PendingFileRenameOperations (Windows 2003+)
PendFileRenVal = PendingFilerenameOperations registry value; used to filter if need be, some Anti-
Virus leverage this key for def/dat removal, giving a false positive PendingReboot
.PARAMETER ComputerName
A single Computer or an array of computer names. The default is localhost ($env:COMPUTERNAME).
.PARAMETER ErrorLog
A single path to send error data to a log file.
.EXAMPLE
PS C:\> Get-PendingReboot -ComputerName (Get-Content C:\ServerList.txt) | Format-Table -AutoSize
Computer CBServicing WindowsUpdate CCMClientSDK PendFileRename PendFileRenVal RebootPending
-------- ----------- ------------- ------------ -------------- -------------- -------------
DC01 False False False False
DC02 False False False False
FS01 False False False False
This example will capture the contents of C:\ServerList.txt and query the pending reboot
information from the systems contained in the file and display the output in a table. The
null values are by design, since these systems do not have the SCCM 2012 client installed,
nor was the PendingFileRenameOperations value populated.
.EXAMPLE
PS C:\> Get-PendingReboot
Computer : WKS01
CBServicing : False
WindowsUpdate : True
CCMClient : False
PendComputerRename : False
PendFileRename : False
PendFileRenVal :
RebootPending : True
This example will query the local machine for pending reboot information.
.EXAMPLE
PS C:\> $Servers = Get-Content C:\Servers.txt
PS C:\> Get-PendingReboot -Computer $Servers | Export-Csv C:\PendingRebootReport.csv -NoTypeInformation
This example will create a report that contains pending reboot information.
.LINK
Component-Based Servicing:
http://technet.microsoft.com/en-us/library/cc756291(v=WS.10).aspx
PendingFileRename/Auto Update:
http://support.microsoft.com/kb/2723674
http://technet.microsoft.com/en-us/library/cc960241.aspx
http://blogs.msdn.com/b/hansr/archive/2006/02/17/patchreboot.aspx
SCCM 2012/CCM_ClientSDK:
http://msdn.microsoft.com/en-us/library/jj902723.aspx
.NOTES
Author: Brian Wilhite
Email: bcwilhite (at) live.com
Date: 29AUG2012
PSVer: 2.0/3.0/4.0/5.0
Updated: 27JUL2015
UpdNote: Added Domain Join detection to PendComputerRename, does not detect Workgroup Join/Change
Fixed Bug where a computer rename was not detected in 2008 R2 and above if a domain join occurred at the same time.
Fixed Bug where the CBServicing wasn't detected on Windows 10 and/or Windows Server Technical Preview (2016)
Added CCMClient property - Used with SCCM 2012 Clients only
Added ValueFromPipelineByPropertyName=$true to the ComputerName Parameter
Removed $Data variable from the PSObject - it is not needed
Bug with the way CCMClientSDK returned null value if it was false
Removed unneeded variables
Added PendFileRenVal - Contents of the PendingFileRenameOperations Reg Entry
Removed .Net Registry connection, replaced with WMI StdRegProv
Added ComputerPendingRename
#>
[CmdletBinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("CN","Computer")]
[String[]]$ComputerName="$env:COMPUTERNAME",
[String]$ErrorLog
)
Begin { }## End Begin Script Block
Process {
Foreach ($Computer in $ComputerName) {
Try {
## Setting pending values to false to cut down on the number of else statements
$CompPendRen,$PendFileRename,$Pending,$SCCM = $false,$false,$false,$false
## Setting CBSRebootPend to null since not all versions of Windows has this value
$CBSRebootPend = $null
## Querying WMI for build version
$WMI_OS = Get-WmiObject -Class Win32_OperatingSystem -Property BuildNumber, CSName -ComputerName $Computer -ErrorAction Stop
## Making registry connection to the local/remote computer
$HKLM = [UInt32] "0x80000002"
$WMI_Reg = [WMIClass] "\\$Computer\root\default:StdRegProv"
## If Vista/2008 & Above query the CBS Reg Key
If ([Int32]$WMI_OS.BuildNumber -ge 6001) {
$RegSubKeysCBS = $WMI_Reg.EnumKey($HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\")
$CBSRebootPend = $RegSubKeysCBS.sNames -contains "RebootPending"
}
## Query WUAU from the registry
$RegWUAURebootReq = $WMI_Reg.EnumKey($HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\")
$WUAURebootReq = $RegWUAURebootReq.sNames -contains "RebootRequired"
## Query PendingFileRenameOperations from the registry
$RegSubKeySM = $WMI_Reg.GetMultiStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\Session Manager\","PendingFileRenameOperations")
$RegValuePFRO = $RegSubKeySM.sValue
## Query JoinDomain key from the registry - These keys are present if pending a reboot from a domain join operation
$Netlogon = $WMI_Reg.EnumKey($HKLM,"SYSTEM\CurrentControlSet\Services\Netlogon").sNames
$PendDomJoin = ($Netlogon -contains 'JoinDomain') -or ($Netlogon -contains 'AvoidSpnSet')
## Query ComputerName and ActiveComputerName from the registry
$ActCompNm = $WMI_Reg.GetStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\","ComputerName")
$CompNm = $WMI_Reg.GetStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\","ComputerName")
If (($ActCompNm -ne $CompNm) -or $PendDomJoin) {
$CompPendRen = $true
}
## If PendingFileRenameOperations has a value set $RegValuePFRO variable to $true
If ($RegValuePFRO) {
$PendFileRename = $true
}
## Determine SCCM 2012 Client Reboot Pending Status
## To avoid nested 'if' statements and unneeded WMI calls to determine if the CCM_ClientUtilities class exist, setting EA = 0
$CCMClientSDK = $null
$CCMSplat = @{
NameSpace='ROOT\ccm\ClientSDK'
Class='CCM_ClientUtilities'
Name='DetermineIfRebootPending'
ComputerName=$Computer
ErrorAction='Stop'
}
## Try CCMClientSDK
Try {
$CCMClientSDK = Invoke-WmiMethod @CCMSplat
} Catch [System.UnauthorizedAccessException] {
$CcmStatus = Get-Service -Name CcmExec -ComputerName $Computer -ErrorAction SilentlyContinue
If ($CcmStatus.Status -ne 'Running') {
Write-Warning "$Computer`: Error - CcmExec service is not running."
$CCMClientSDK = $null
}
} Catch {
$CCMClientSDK = $null
}
If ($CCMClientSDK) {
If ($CCMClientSDK.ReturnValue -ne 0) {
Write-Warning "Error: DetermineIfRebootPending returned error code $($CCMClientSDK.ReturnValue)"
}
If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending) {
$SCCM = $true
}
}
Else {
$SCCM = $null
}
## Creating Custom PSObject and Select-Object Splat
$SelectSplat = @{
Property=(
'Computer',
'CBServicing',
'WindowsUpdate',
'CCMClientSDK',
'PendComputerRename',
'PendFileRename',
'PendFileRenVal',
'RebootPending'
)}
New-Object -TypeName PSObject -Property @{
Computer=$WMI_OS.CSName
CBServicing=$CBSRebootPend
WindowsUpdate=$WUAURebootReq
CCMClientSDK=$SCCM
PendComputerRename=$CompPendRen
PendFileRename=$PendFileRename
PendFileRenVal=$RegValuePFRO
RebootPending=($CompPendRen -or $CBSRebootPend -or $WUAURebootReq -or $SCCM -or $PendFileRename)
} | Select-Object @SelectSplat
} Catch {
Write-Warning "$Computer`: $_"
## If $ErrorLog, log the file to a user specified location/path
If ($ErrorLog) {
Out-File -InputObject "$Computer`,$_" -FilePath $ErrorLog -Append
}
}
}## End Foreach ($Computer in $ComputerName)
}## End Process
End { }## End End
}## End Function Get-PendingReboot
Add-Content $logFile -value ("$(get-date -f s) Check for Windows Update PowerShell Module") -PassThru | Write-Output
#If module is missing, download the Windows Update PowerShell module
if(-not (Test-Path "$scriptPath\PSWindowsUpdate")){
Add-Content $logFile -value ("$(get-date -f s) Windows Update PowerShell Module not downloaded") -PassThru | Write-Output
iwr -Uri "https://gallery.technet.microsoft.com/scriptcenter/2d191bcd-3308-4edd-9de2-88dff796b0bc/file/41459/43/PSWindowsUpdate.zip" -OutFile $env:temp\PSWindowsUpdate.zip
#Unzip the archive
if(Get-Command Expand-Archive -ErrorAction SilentlyContinue){
Expand-Archive $env:temp\PSWindowsUpdate.zip -DestinationPath $scriptPath
}else{
Add-Type -A 'System.IO.Compression.FileSystem';
[IO.Compression.ZipFile]::ExtractToDirectory("$env:temp\PSWindowsUpdate.zip", "$scriptPath")
}
}
Add-Content $logFile -value ("$(get-date -f s) Import Windows Update PowerShell Module") -PassThru | Write-Output
#Import the Module
Import-Module "$scriptPath\PSWindowsUpdate\PSWindowsUpdate.psm1"
Add-Content $logFile -value ("$(get-date -f s) Getting Windows Update List") -PassThru | Write-Output
#Check to see if there are any updates left
[arrray] $list = Get-WUList -WindowsUpdate
while($list.Count -gt 0){
Add-Content $logFile -value ("$(get-date -f s) Found $($list.Count)") -PassThru | Write-Output
foreach($item in $list){
Add-Content $logFile -value ("$(get-date -f s) KB: $($item.KB) Update: $($item.Title)") -PassThru | Write-Output
}
#Get and install updates if there are any left
Add-Content $logFile -value ("$(get-date -f s) Get the updates and install them") -PassThru | Write-Output
$installs = Get-WUInstall -WindowsUpdate -AcceptAll -AutoReboot
#Some updates need a reboot, even though they don't say so.
Add-Content $logFile -value ("$(get-date -f s) Check if a reboot is pending") -PassThru | Write-Output
$pendingReboot = Get-PendingReboot
if($pendingReboot.WindowsUpdate -or $pendingReboot.RebootPending){
Add-Content $logFile -value ("$(get-date -f s) Reboot is pending, restarting computer") -PassThru | Write-Output
Restart-Computer -Force
}
#Check for failed installs, restart computer usually fixes
$failed = $installs | ?{$_.Status -eq "Failed"}
if($failed){
Add-Content $logFile -value ("$(get-date -f s) At least one update failed, restarting computer") -PassThru | Write-Output
Restart-Computer -Force
}
Add-Content $logFile -value ("$(get-date -f s) No reboot, loading the list again") -PassThru | Write-Output
[arrray] $list = Get-WUList -WindowsUpdate
}
if(($list.Count -eq 0) -or ($list -eq $null)){
Add-Content $logFile -value ("$(get-date -f s) No list items left, removing scheduled task") -PassThru | Write-Output
$task = Get-ScheduledTask -TaskName "WindowsUpdatePowerShell" -ErrorAction SilentlyContinue
if($task){
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false
}
Add-Content $logFile -value ("$(get-date -f s) ALL WINDOWS UPDATES INSTALLED") -PassThru | Write-Output
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment