Although this function requires ‘interactive’ or console sessions, it can be trigged by Windows Scheduled Tasks. Hence, it is included inside a more comprehensive program here.
# updateLocalWindowsUsingComObjects.ps1
# Version: 0.0.2
# This iteration includes the feature to output a log of update progress
function updateLocalWindowsUsingComObjects($autoReboot=$false,$logfile='c:\WindowsUpdate.log'){
# in case contents of function is called from scheduled tasks without any default argument values
$logFile=if($logFile){$logFile}else{'c:\WindowsUpdate.log'}
if(!(test-path $logfile)){
new-item $logfile -type file -force
}
$status="$(get-date) => $env:computername patching STARTED"
write-host $status
Add-Content -Path $logfile -Value $status
$updateSession=New-Object -Com Microsoft.Update.Session
$updateSearcher=$updateSession.CreateUpdateSearcher()
$searchCriteria="IsInstalled=0 AND Type='Software'" # "IsHidden=0 AND IsInstalled=0 AND Type='Software'"
$availableUpdates=$updateSearcher.Search($searchCriteria)
$availableUpdatesCount=$availableUpdates.Updates.count
if($availableUpdatesCount -eq 0){
$status="$(get-date) => $env:computername has 0 available updates. Patching FINISHED"
write-host $status
Add-Content -Path $logfile -Value $status
return $true
}else{
$status="$(get-date) => $availableUpdatesCount available updates detected"
write-host $status
Add-Content -Path $logfile -Value $status
$updatesToDownload=New-Object -Com Microsoft.Update.UpdateColl
For ($i=0; $i -lt $availableUpdates.Updates.Count; $i++){
$item = $availableUpdates.Updates.Item($i)
$Null = $updatesToDownload.Add($item)
}
$updateSession=New-Object -Com Microsoft.Update.Session
$downloader=$updateSession.CreateUpdateDownloader()
$downloader.Updates=$updatesToDownload
try{
$null=$downloader.Download()
}catch{
write-warning $_
}
$downloadedUpdates=New-Object -Com Microsoft.Update.UpdateColl
For ($i=0; $i -lt $availableUpdates.Updates.Count; $i++){
$item = $availableUpdates.Updates.Item($i)
If ($item.IsDownloaded) {
$null=$downloadedUpdates.Add($item)
}
}
$downloadedCount=$downloadedUpdates.count
if($downloadedCount -eq 0){
$status="$(get-date) => Installation cannot proceed 0 successful downloads."
write-host $status
Add-Content -Path $logfile -Value $status
return $false
}else{
$status="$(get-date) => $downloadedCount of $availableUpdatesCount updates downloaded successfully"
write-host $status
Add-Content -Path $logfile -Value $status
$installCount=0
foreach($update in $downloadedUpdates){
$thisUpdate = New-object -com "Microsoft.Update.UpdateColl"
$thisCount=$installCount++ +1
$null=$thisUpdate.Add($update)
$installer = $updateSession.CreateUpdateInstaller()
$installer.Updates = $thisUpdate
$installResult = $installer.Install()
$translatedCode=switch ($installResult.ResultCode){
0 {'not started'}
1 {'in progress'}
2 {'succeeded'}
3 {'succeeded with errors'}
4 {'failed'}
5 {'aborted'}
}
$status="$(get-date) => $thisCount of $downloadedCount $translatedCode`: $($update.Title)"
write-host $status
Add-Content -Path $logfile -Value $status
}
}
}
$pendingReboot=.{
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
try {
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if (($null -ne $status) -and $status.RebootPending) {
return $true
}
}catch{}
return $false
}
If($pendingReboot){
if($autoReboot){
$status="$(get-date) => $env:computername REBOOTED"
write-host $status
Add-Content -Path $logfile -Value $status
(Get-WMIObject -Class Win32_OperatingSystem).Reboot()
}else{
$status="$(get-date) => $env:computername required a REBOOT"
write-host $status
Add-Content -Path $logfile -Value $status
}
}else{
$status="$(get-date) => $env:computername patching FINISHED"
write-host $status
Add-Content -Path $logfile -Value $status
}
}
updateLocalWindowsUsingComObjects
Note: this function only works when login interactively – it does not work via WinRM or this error would occur:
# $computernames='testWindows'
# $autoreboot=$true
# function updateLocalWindowsUsingComObjects{...}
# $computerNames|%{invoke-command -computername $_ -scriptblock{
# param($updateLocalWindows,$autoreboot)
# write-host "Updating $env:computername..."
# [scriptblock]::create($updateLocalWindows).invoke($autoreboot)
# } -args ${function:updateLocalWindows},$autoreboot
# }
# This occurs because of 2nd-hop issues - COM objects require interactive logons
#
# Exception calling "Invoke" with "1" argument(s): "Access is denied. (Exception from HRESULT: 0x80070005
# (E_ACCESSDENIED))"
# + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
# + FullyQualifiedErrorId : RuntimeException
# + PSComputerName : testWindows
#
# This is an unsecured workaround:
# $username='domain\admin'
# $plaintextPassword='PASSWORD'
# $encryptedPassword = ConvertTo-SecureString $plaintextPassword -AsPlainText -Force
# $credentials = New-Object System.Management.Automation.PSCredential $username,$encryptedPassword
# Start-Process -Credential $credentials powershell -ArgumentList "-Command & {...}"
Categories: