Skip to content

Instantly share code, notes, and snippets.

@mhudasch
Last active June 4, 2024 14:04
Show Gist options
  • Save mhudasch/7faf56c16659a666bdb1aa89252142dd to your computer and use it in GitHub Desktop.
Save mhudasch/7faf56c16659a666bdb1aa89252142dd to your computer and use it in GitHub Desktop.
Installs all pending windows updates or updates piped from Get-PendingWindowsUpdate.
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