Last active
June 4, 2024 14:04
-
-
Save mhudasch/7faf56c16659a666bdb1aa89252142dd to your computer and use it in GitHub Desktop.
Installs all pending windows updates or updates piped from Get-PendingWindowsUpdate.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Function Install-WindowsUpdate { | |
[CmdletBinding(SupportsShouldProcess = $true)] | |
param( | |
[Parameter(Position = 0, Mandatory=$false, ValueFromPipeline = $true, ParameterSetName="computer")] | |
[string[]]$ComputerName = $env:COMPUTERNAME, | |
[Parameter(Position = 0, Mandatory=$true, ValueFromPipeline = $true, ParameterSetName="updates")] | |
[PSCustomObject[]]$PendingUpdates) | |
Process { | |
# Use this scriptblock for both remote and local execution | |
$unifiedScriptBlock = [scriptblock]{ | |
param([string]$computer, [string]$filter) | |
$VerbosePreference = $using:VerbosePreference; | |
$ConfirmPreference = $using:ConfirmPreference; | |
$result = @{ | |
Computer=$computer; | |
Error=$null; | |
UpdateResults=$null; | |
}; | |
Try { | |
# Only do this when you are in the local administrators group | |
If (!([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { | |
throw "You are not running this as an Administrator!`nPlease re-run this with an Administrator account."; | |
} | |
# Don't go further if the machine already needs a reboot because of an earlier update. | |
IF (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { | |
throw "There is an update related reboot pending. Please reboot the machine an perform this command again." | |
} | |
$scheduledJobName = ("Arv-WinUpdInstall-" + (Get-Date -f "yyyyMMdd-HHmmss")); | |
# There is a bug in the COM - Interface for Windows Updates which gives us HRESULT: 0x80070005 E_ACCESSDENIED errors when | |
# creating either an UpdateDownloader or an UpdateInstaller. The workaround is to create a scheduled job. | |
# see http://stackoverflow.com/questions/7078958/powershell-remote-microsoft-update-session-access-denied-0x80070005 | |
$opts = New-ScheduledJobOption -RunElevated -RequireNetwork; | |
$scheduledJob = Register-ScheduledJob -Name $scheduledJobName -ScheduledJobOption $opts -RunNow -ScriptBlock { | |
param([string]$computer,[string]$filter) | |
$ErrorActionPreference = 'Stop'; | |
#Create Session COM object | |
$updateSession = New-Object -ComObject microsoft.update.session; | |
#Configure Session COM Object | |
$updateSearcher = $updateSession.CreateUpdateSearcher(); | |
#Configure Searcher object to look for Updates awaiting installation | |
$searchResult = $updateSearcher.Search($filter); | |
If($searchResult.Updates.Count -eq 0) { | |
Return $null; | |
} | |
#Create Downloader COM object | |
$downloader = $updateSession.CreateUpdateDownloader(); | |
#Configure Downloader | |
$downloader.Updates = $searchResult.Updates; | |
$downloader.Download() | Out-Null; | |
$downloaded = $searchResult.Updates | Where-Object { $_.IsDownloaded }; | |
#Create Installer COM object | |
$installer = $updateSession.CreateUpdateInstaller() ; | |
#Configure Installer | |
$installer.Updates = $downloaded; | |
$installationResult = $installer.Install(); | |
Return New-Object psobject -Property @{ | |
DownloadResult = New-Object psobject -Property @{ | |
HResult = $downloadResult.HResult; | |
ResultCode = $downloadResult.ResultCode; | |
}; | |
InstallationResult = New-Object psobject -Property @{ | |
HResult = $installationResult.HResult; | |
RebootRequired = $installationResult.RebootRequired; | |
ResultCode = $installationResult.ResultCode; | |
UpdateResults = $($searchResult.Updates | ForEach-Object {$i=0} { | |
$update = $searchResult.Updates.Item($i); | |
$ur = $installationResult.GetUpdateResult($i); | |
$i++; | |
New-Object psobject -Property @{ | |
Title = $update.Title; | |
KB = $($update.KBArticleIDs); | |
SecurityBulletin = $($update.SecurityBulletinIDs); | |
MsrcSeverity = $update.MsrcSeverity; | |
Url = $($update.MoreInfoUrls); | |
Categories = ($update.Categories | Select-Object -ExpandProperty Name); | |
RebootRequired = $update.RebootRequired; | |
MaxDownloadSize = $update.MaxDownloadSize; | |
MinDownloadSize = $update.MinDownloadSize; | |
ID = $update.Identity.UpdateID; | |
Revision = $update.Identity.RevisionNumber; | |
HResult = $ur.HResult; | |
ResultCode = $ur.ResultCode; | |
}; | |
}); | |
}; | |
}; | |
} -ArgumentList @( $computer, $filter ); | |
$jobInstance = (Get-Job -Name $scheduledJob.Name -ErrorAction SilentlyContinue); | |
while($null -eq $jobInstance) { | |
Start-Sleep -Seconds 1; | |
$jobInstance = (Get-Job -Name $scheduledJob.Name -ErrorAction SilentlyContinue); | |
} | |
$jobInstance | Wait-Job -ErrorAction Stop | Out-Null; | |
Start-Sleep -Seconds 1; | |
Try { | |
$scheduledJobResult = $jobInstance | Receive-Job; | |
} Catch { | |
$result.Error = $_; | |
return $result; | |
} Finally { | |
$scheduledJob | Unregister-ScheduledJob | Out-Null; | |
} | |
$result.UpdateResults=$scheduledJobResult; | |
return $result; | |
} Catch { | |
$result.Error = $_; | |
return $result; | |
} | |
}; | |
If ($PSCmdlet.ParameterSetName -eq "computer") { | |
# all updates | |
$updateFilter = "IsInstalled=0 and IsHidden=0 and Type='Software'"; | |
# apply filter on all computers | |
$updateRequests = $ComputerName | ForEach-Object { return @{ Computer=$_; Filter=$updateFilter; } }; | |
} ElseIf ($PSCmdlet.ParameterSetName -eq "updates") { | |
# differentiate passed updates and get-pendingwindowsupdate result sets and flatten the list | |
$updatesToInstall = $PendingUpdates | ForEach-Object { If($null -ne $_.Updates){ $_.Updates; } Else { if(!$_.Error) { $_; } } }; | |
# group by computer | |
$grouped = $updatesToInstall | Group-Object -Property Computer; | |
# create filter for each computer for each update | |
$updateRequests = $grouped | ForEach-Object { return @{ Computer=$_.Name; Filter=$("IsInstalled=0 and IsHidden=0 and Type='Software' and " + [string]::Join(" or ", $($_.Group | ForEach-Object { ("(UpdateID='{0}' and RevisionNumber={1})" -f $_.ID,$_.Revision); } ))); }; }; | |
} | |
$results = $updateRequests | ForEach-Object { $request = $_; | |
Try { | |
If(!(Test-Connection -ComputerName $request.Computer -Count 1 -Quiet)) { | |
throw ("Connecting to remote computer {0} failed." -f $request.Computer); | |
} Else { | |
If($request.Computer -eq $env:COMPUTERNAME) { | |
If($PSCmdlet.ShouldProcess(("Computer: {0}" -f $request.Computer), "Install Windows Updates.")) { | |
Start-Job -ScriptBlock $unifiedScriptBlock -ArgumentList @($request.Computer, $request.Filter); | |
} | |
} Else { | |
If($PSCmdlet.ShouldProcess(("Computer: {0}" -f $request.Computer), "Install Windows Updates.")) { | |
Invoke-Command -ComputerName $request.Computer -ScriptBlock $unifiedScriptBlock -ArgumentList @($request.Computer, $request.Filter) -AsJob -HideComputerName; | |
} | |
} | |
} | |
} Catch { | |
Return @{ | |
ComputerName=$request.Computer; | |
Error=$_; | |
}; | |
} | |
}; | |
$jobs = $results | Where-Object { $_ -is [System.Management.Automation.Job] }; | |
$jobs | Wait-Job | Out-Null; | |
$u = @($results | Where-Object { $_ -is [hashtable] } | ForEach-Object { New-Object psobject -Property $_; }); | |
$jobs | Receive-Job | ForEach-Object { | |
$u += $_; | |
}; | |
$jobs | Remove-Job | Out-Null; | |
# hide the workflow meta data | |
Return [PsObject[]]($u | ForEach-Object { (New-Object psobject -Property $_) | Select-Object -Property * -ExcludeProperty PSComputerName,PSSourceJobInstanceId,RunspaceId,PSShowComputerName }); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment