dist/temp/WindowsUpdateTools/Public/Install-WindowsUpdate.ps1
|
function Install-WindowsUpdate { <# .SYNOPSIS Downloads and installs Windows Updates using the Microsoft Update API. .DESCRIPTION This function uses the Microsoft.Update.Session COM object to search for, download, and install Window # Download u # Download updates The download process is optimized using BITS (Background Intelligent Transfer Service) with high priority and forced execution to bypass throttling, following best practices from the PSWindowsUpdate module. It provides comprehensive logging, error handling, and integration with the WindowsUpdateTools module's diagnostic capabilities. .PARAMETER Criteria The search criteria for updates. Default searches for critical updates that are not hidden or already installed. .PARAMETER AcceptEula Automatically accept End User License Agreements for updates that require them. .PARAMETER IncludeDrivers Include driver updates in the search. By default, only software updates are included. .PARAMETER SuppressReboot Suppress automatic reboot even if updates require it. A warning will be logged instead. .PARAMETER UseWindowsUpdate Force the use of Windows Update servers instead of WSUS (if configured). .PARAMETER DownloadPriority Sets the BITS download priority (1=Low, 2=Normal, 3=High, 4=ExtraHigh). Default is 4 for fastest downloads. .PARAMETER DisableForcedDownload Disables forced download mode. By default, downloads bypass BITS throttling for faster performance. .PARAMETER MaxRetries Maximum number of retry attempts for downloads that fail. Default is 3. .PARAMETER RetryDelay Delay in seconds between retry attempts. Default is 30 seconds. .EXAMPLE Install-WindowsUpdate Installs all available critical software updates. .EXAMPLE Install-WindowsUpdate -IncludeDrivers -AcceptEula Installs all available updates including drivers, automatically accepting any EULAs. .EXAMPLE Install-WindowsUpdate -Criteria "IsInstalled=0 and Type='Software' and BrowseOnly=0" -SuppressReboot Installs software updates with custom criteria and suppresses automatic reboot. .EXAMPLE Install-WindowsUpdate -DownloadPriority 2 -DisableForcedDownload Installs updates with normal BITS priority and standard throttling (for bandwidth-limited environments). .OUTPUTS System.Object Returns an object containing installation results and statistics. .NOTES Requires administrative privileges and the Windows Update service to be available. Uses the Microsoft.Update.Session COM object which is part of the Windows Update Agent. Provides user-friendly error messages for common Windows Update failures. Author: CSOLVE Scripts Version: 1.5.7 #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter()] [string]$Criteria = "IsHidden=0 and IsInstalled=0 and Type='Software' and BrowseOnly=0", [Parameter()] [switch]$AcceptEula, [Parameter()] [switch]$IncludeDrivers, [Parameter()] [switch]$SuppressReboot, [Parameter()] [switch]$UseWindowsUpdate, [Parameter()] [ValidateRange(1, 4)] [int]$DownloadPriority = 4, [Parameter()] [switch]$DisableForcedDownload, [Parameter()] [int]$MaxRetries = 3, [Parameter()] [int]$RetryDelay = 30 ) $result = @{ Success = $false SearchCompleted = $false UpdatesFound = 0 UpdatesDownloaded = 0 UpdatesInstalled = 0 UpdatesFailed = 0 RebootRequired = $false Errors = @() InstalledUpdates = @() FailedUpdates = @() Duration = $null } $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() try { Write-WULog "Starting Windows Update installation process" -Level Info # Check if running as administrator if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw "This function requires administrative privileges. Please run as administrator." } # Modify criteria to include drivers if requested if ($IncludeDrivers) { $Criteria = $Criteria -replace "Type='Software'", "(Type='Software' or Type='Driver')" Write-WULog "Including driver updates in search criteria" -Level Info } Write-WULog "Using search criteria: $Criteria" -Level Info # Initialize COM objects Write-WULog "Initializing Windows Update Session" -Level Info $updateSession = New-Object -ComObject "Microsoft.Update.Session" $updateSearcher = $updateSession.CreateUpdateSearcher() $updateDownloader = $updateSession.CreateUpdateDownloader() $updateInstaller = $updateSession.CreateUpdateInstaller() # PATCH: Configure downloader properties immediately after creation (like debug tool) # Moving this early prevents COM corruption that occurs when set right before download try { # CRITICAL: Read current values first to stabilize COM object (like debug tool does) $currentPriority = $updateDownloader.Priority $currentIsForced = $updateDownloader.IsForced Write-WULog "Current downloader state: Priority=$currentPriority, IsForced=$currentIsForced" -Level Verbose $updateDownloader.Priority = $DownloadPriority # 1=Low, 2=Normal, 3=High, 4=ExtraHigh $updateDownloader.IsForced = -not $DisableForcedDownload.IsPresent # Force download, bypass BITS throttling Write-WULog "Early downloader configuration: Priority=$DownloadPriority, IsForced=$(-not $DisableForcedDownload.IsPresent)" -Level Verbose } catch { Write-WULog "ERROR: Failed to configure downloader properties: $($_.Exception.Message)" -Level Error throw "Failed to configure downloader properties: $($_.Exception.Message)" } # Configure update source if ($UseWindowsUpdate) { $updateSearcher.ServerSelection = 2 # Use Microsoft Update servers Write-WULog "Configured to use Microsoft Update servers" -Level Info } else { $updateSearcher.ServerSelection = 0 # Use default (may be WSUS if configured) Write-WULog "Using default update server configuration" -Level Info } # Manage Windows Update service and dependencies $wuService = Get-Service -Name "wuauserv" -ErrorAction SilentlyContinue if (-not $wuService) { throw "Windows Update service (wuauserv) not found" } # Check related services that may affect downloads $bitsService = Get-Service -Name "BITS" -ErrorAction SilentlyContinue $cryptsvcService = Get-Service -Name "cryptsvc" -ErrorAction SilentlyContinue Write-WULog "Service Status Check:" -Level Info Write-WULog " Windows Update (wuauserv): $($wuService.Status)" -Level Info if ($bitsService) { Write-WULog " BITS: $($bitsService.Status)" -Level Info } if ($cryptsvcService) { Write-WULog " Cryptographic Services: $($cryptsvcService.Status)" -Level Info } $originalServiceState = $wuService.Status $originalStartType = $wuService.StartType Write-WULog "Windows Update service state: $originalServiceState, Start type: $originalStartType" -Level Info # Ensure required services are running if ($wuService.Status -ne "Running") { if ($wuService.StartType -eq "Disabled") { Write-WULog "Temporarily enabling Windows Update service" -Level Warning Set-Service -Name "wuauserv" -StartupType Manual } Write-WULog "Starting Windows Update service" -Level Info Start-Service -Name "wuauserv" -ErrorAction Stop } # Ensure BITS is running (required for downloads) if ($bitsService -and $bitsService.Status -ne "Running") { Write-WULog "Starting BITS service (required for downloads)" -Level Info try { Start-Service -Name "BITS" -ErrorAction Stop } catch { Write-WULog "Warning: Could not start BITS service: $($_.Exception.Message)" -Level Warning } } # Wait for service to fully start $timeout = 30 $elapsed = 0 while ((Get-Service -Name "wuauserv").Status -ne "Running" -and $elapsed -lt $timeout) { Start-Sleep -Seconds 1 $elapsed++ } if ((Get-Service -Name "wuauserv").Status -ne "Running") { throw "Failed to start Windows Update service within $timeout seconds" } # Test COM object before proceeding if (-not (Test-WUCOMObject)) { Write-WULog -Level Error -Message "Core Windows Update components are not available. Aborting." return } # Search for updates Write-WULog "Searching for updates..." -Level Info $searchResult = $updateSearcher.Search($Criteria) $result.SearchCompleted = $true $result.UpdatesFound = $searchResult.Updates.Count Write-WULog "Found $($searchResult.Updates.Count) updates" -Level Info if ($searchResult.Updates.Count -eq 0) { Write-WULog "No updates found matching the specified criteria" -Level Info return $result } # Process each update $updateCollection = New-Object -ComObject "Microsoft.Update.UpdateColl" $updatesToProcess = @() foreach ($update in $searchResult.Updates) { $updateInfo = @{ Title = $update.Title Description = $update.Description SizeInMB = [math]::Round($update.MaxDownloadSize / 1MB, 2) SecurityBulletinIDs = $update.SecurityBulletinIDs KBArticleIDs = $update.KBArticleIDs Update = $update } # Handle EULA acceptance if (-not $update.EulaAccepted) { if ($AcceptEula) { Write-WULog "Accepting EULA for update: $($update.Title)" -Level Warning $update.AcceptEula() } else { Write-WULog "Skipping update (EULA not accepted): $($update.Title)" -Level Warning $result.Errors += "EULA not accepted for update: $($update.Title)" continue } } # Check if update requires user input if ($update.InstallationBehavior.CanRequestUserInput) { Write-WULog "Skipping update (requires user input): $($update.Title)" -Level Warning $result.Errors += "Update requires user input: $($update.Title)" continue } $updatesToProcess += $updateInfo $updateCollection.Add($update) | Out-Null } if ($updateCollection.Count -eq 0) { Write-WULog "No updates available for installation after filtering" -Level Info return $result } # Download updates Write-WULog "Downloading updates..." -Level Info $updateDownloader.Updates = $updateCollection # CRITICAL: Read Updates.Count immediately after assignment to stabilize COM object state # This prevents 0x80131501 - the count access "solidifies" the COM object after assignment try { $updatesCount = $updateDownloader.Updates.Count Write-WULog "Successfully assigned $updatesCount updates to downloader" -Level Verbose } catch { Write-WULog "WARNING: Could not read updates count after assignment - may indicate COM corruption" -Level Warning } # PATCH: Removed late Priority/IsForced configuration to prevent COM corruption # Configuration moved to early initialization (right after CreateUpdateDownloader) # Original problematic code was here: # $updateDownloader.Priority = $DownloadPriority # $updateDownloader.IsForced = -not $DisableForcedDownload.IsPresent Write-WULog "Priority/IsForced configuration moved to early initialization" -Level Verbose $priorityName = @{1="Low"; 2="Normal"; 3="High"; 4="ExtraHigh"}[$DownloadPriority] $forcedStatus = if (-not $DisableForcedDownload.IsPresent) { "Enabled" } else { "Disabled" } Write-WULog "Configured downloader: Priority=$priorityName, ForcedDownload=$forcedStatus (BITS optimization)" -Level Info # Calculate total download size for progress tracking $totalSizeBytes = 0 foreach ($updateInfo in $updatesToProcess) { $totalSizeBytes += ($updateInfo.SizeInMB * 1MB) } $totalSizeMB = [math]::Round($totalSizeBytes / 1MB, 2) if ($totalSizeMB -gt 0) { Write-WULog "Total download size: $totalSizeMB MB" -Level Info } $downloadSuccess = $false $retryCount = 0 while (-not $downloadSuccess -and $retryCount -lt $MaxRetries) { try { $retryCount++ Write-WULog "Download attempt $retryCount of $MaxRetries" -Level Info # Validate update collection before download Write-WULog "Validating update collection..." -Level Verbose # PATCH: Removed count access to prevent COM corruption Write-WULog "Update collection validation complete (count access removed to prevent 0x80131501)" -Level Verbose Write-WULog "Update downloader object validated (type access removed to prevent COM corruption)" -Level Verbose # PATCH: Removed updateCollection.Count check to prevent COM corruption # if ($updateCollection.Count -eq 0) { # throw "Update collection is empty - cannot download" # } # PATCH: Removed update validation loop to prevent COM corruption # for ($i = 0; $i -lt $updateCollection.Count; $i++) { # $update = $updateCollection.Item($i) # Write-WULog "Update $($i + 1): $($update.Title)..." -Level Verbose # } Write-WULog "Update validation skipped to prevent COM corruption" -Level Verbose # Start download with progress monitoring (direct execution - no background job) $downloadStartTime = Get-Date Write-Progress -Activity "Downloading Windows Updates" -Status "Starting download..." -PercentComplete 0 Write-WULog "Starting download of updates..." -Level Info # Execute download directly (COM objects can't be serialized to background jobs) Write-WULog "STEP 1: About to call updateDownloader.Download()..." -Level Verbose # PATCH: Removed problematic property access validation that was corrupting COM object # The following debug lines were causing 0x80131501 errors: # - UpdateDownloader.Updates.Count access # - Other property accesses right before Download() Write-WULog "STEP 2: All property access validation removed to prevent COM corruption" -Level Verbose # Add granular debugging to pinpoint exact failure location Write-WULog "STEP 3: Starting download attempt with minimal COM interaction..." -Level Verbose Write-WULog "STEP 4: No property reads, no validation, direct call only" -Level Verbose # Attempt download with detailed error reporting Write-WULog "STEP 5: Entering try block for Download() call" -Level Verbose try { Write-WULog "STEP 6: Inside try block, about to execute Download() method" -Level Verbose Write-WULog "STEP 7: Calling updateDownloader.Download() NOW..." -Level Verbose $downloadResult = $updateDownloader.Download() Write-WULog "STEP 8: Download() method completed successfully!" -Level Verbose Write-WULog "Download method returned successfully" -Level Verbose Write-WULog "DEBUG: Download result type: $($downloadResult.GetType().FullName)" -Level Verbose Write-WULog "DEBUG: Download result code: $($downloadResult.ResultCode)" -Level Verbose } catch [System.Management.Automation.MethodInvocationException] { Write-WULog "ERROR: STEP FAILED - COM Method Invocation Exception during Download() call" -Level Error Write-WULog "ERROR: This means the Download() method itself failed to execute" -Level Error Write-WULog "ERROR: Exception Message: $($_.Exception.Message)" -Level Error Write-WULog "ERROR: Inner Exception: $($_.Exception.InnerException.Message)" -Level Error Write-WULog "ERROR: HResult: $($_.Exception.HResult)" -Level Error throw "Download failed due to COM method invocation error: $($_.Exception.Message)" } catch [System.Runtime.InteropServices.COMException] { Write-WULog "ERROR: COM Exception during download" -Level Error Write-WULog "ERROR: Exception Message: $($_.Exception.Message)" -Level Error Write-WULog "ERROR: HResult: $($_.Exception.HResult)" -Level Error throw "Download failed due to COM error: $($_.Exception.Message)" } catch { Write-WULog "ERROR: Unexpected exception during download" -Level Error Write-WULog "ERROR: Exception Type: $($_.Exception.GetType().FullName)" -Level Error Write-WULog "ERROR: Exception Message: $($_.Exception.Message)" -Level Error Write-WULog "ERROR: HResult: $($_.Exception.HResult)" -Level Error Write-WULog "ERROR: Stack Trace: $($_.Exception.StackTrace)" -Level Error throw "Download failed: $($_.Exception.Message)" } Write-Progress -Activity "Downloading Windows Updates" -Status "Download completed" -PercentComplete 100 $downloadEndTime = Get-Date $downloadDuration = $downloadEndTime - $downloadStartTime Write-WULog "Download completed in $($downloadDuration.ToString('mm\:ss'))" -Level Info # Verify download completion $allDownloaded = $true for ($i = 0; $i -lt $updateCollection.Count; $i++) { $update = $updateCollection.Item($i) if (-not $update.IsDownloaded) { $allDownloaded = $false Write-WULog "Update not fully downloaded: $($update.Title)" -Level Warning } } if ($allDownloaded) { $downloadSuccess = $true Write-WULog "Download completed successfully - all updates verified as downloaded" -Level Info } else { $result.Errors += "Verification failed: Some updates were not properly downloaded." Write-WULog "ERROR: Verification failed. Not all updates were downloaded successfully." -Level Error # Do not throw here, let the process continue to the finally block to report results } } catch { $currentHResult = if ($_.Exception.HResult -ne 0) { $_.Exception.HResult } else { $_.Exception.Message } # Enhanced error logging Write-WULog "Exception details:" -Level Verbose Write-WULog " Exception Type: $($_.Exception.GetType().FullName)" -Level Verbose Write-WULog " HResult: $($_.Exception.HResult)" -Level Verbose Write-WULog " Message: $($_.Exception.Message)" -Level Verbose Write-WULog " InnerException: $($_.Exception.InnerException)" -Level Verbose if ($currentHResult -eq -2145124325) { # WU_E_SELFUPDATE_IN_PROGRESS Write-WULog "Windows Update Agent is self-updating. Retry $retryCount of $MaxRetries in $RetryDelay seconds..." -Level Warning } elseif ($currentHResult -eq -2146233087) { # Common .NET exception - often indicates COM object issues Write-WULog "COM object method invocation failed. This may indicate:" -Level Error Write-WULog " - Update collection is empty or invalid" -Level Error Write-WULog " - UpdateDownloader COM object is corrupted" -Level Error Write-WULog " - Windows Update service is not running properly" -Level Error Write-WULog "Attempting to recreate COM objects..." -Level Info # PATCH: Avoid COM object recreation and property assignment that causes corruption try { Write-WULog "PATCH: Skipping COM recreation to prevent 0x80131501 corruption" -Level Info # Original problematic code: # $updateSession = New-Object -ComObject "Microsoft.Update.Session" # $updateDownloader = $updateSession.CreateUpdateDownloader() # $updateDownloader.Updates = $updateCollection # <-- THIS CORRUPTS! # $updateDownloader.Priority = $DownloadPriority # $updateDownloader.IsForced = -not $DisableForcedDownload.IsPresent Write-WULog "COM object recreation skipped to prevent corruption" -Level Info } catch { Write-WULog "Failed to recreate COM objects: $($_.Exception.Message)" -Level Error } } else { if ($_.Exception.HResult -ne 0) { $errorDescription = Get-WUHResultDescription -HResult $_.Exception.HResult } else { $errorDescription = "Download failed - $($_.Exception.Message)" } Write-WULog "Download failed (attempt $retryCount of $MaxRetries): $errorDescription" -Level Error } if ($retryCount -lt $MaxRetries) { Start-Sleep -Seconds $RetryDelay } else { if ($_.Exception.HResult -ne 0) { $errorDescription = Get-WUHResultDescription -HResult $_.Exception.HResult throw "Download failed after $MaxRetries attempts: $errorDescription" } else { throw "Download failed after $MaxRetries attempts - $($_.Exception.Message)" } } } } $result.UpdatesDownloaded = $updateCollection.Count # Install updates if ($PSCmdlet.ShouldProcess("$($updateCollection.Count) Windows Updates", "Install")) { # Final validation - ensure all updates are downloaded before installation $notDownloaded = @() for ($i = 0; $i -lt $updateCollection.Count; $i++) { $update = $updateCollection.Item($i) if (-not $update.IsDownloaded) { $notDownloaded += $update.Title } } if ($notDownloaded.Count -gt 0) { $errorMsg = "Cannot install updates that are not fully downloaded: $($notDownloaded -join ', ')" Write-WULog $errorMsg -Level Error $result.Errors += $errorMsg throw $errorMsg } Write-WULog "Installing $($updateCollection.Count) updates..." -Level Info $updateInstaller.Updates = $updateCollection try { # Start installation with progress monitoring (direct execution - no background job) $installStartTime = Get-Date Write-Progress -Activity "Installing Windows Updates" -Status "Starting installation..." -PercentComplete 0 Write-WULog "Starting installation..." -Level Info # Execute installation directly (COM objects can't be serialized to background jobs) $installResult = $updateInstaller.Install() Write-Progress -Activity "Installing Windows Updates" -Status "Installation completed" -PercentComplete 100 Write-Progress -Activity "Installing Windows Updates" -Completed $installEndTime = Get-Date $installDuration = $installEndTime - $installStartTime Write-WULog "Installation completed in $($installDuration.ToString('mm\:ss'))" -Level Info # Debug installation result object Write-WULog "Installation result object type: $($installResult.GetType().FullName)" -Level Verbose Write-WULog "Installation result code: $($installResult.ResultCode)" -Level Verbose Write-WULog "Number of update results: $($installResult.GetUpdateResult.Count)" -Level Verbose Write-WULog "Reboot required: $($installResult.RebootRequired)" -Level Verbose # Process installation results for ($i = 0; $i -lt $installResult.GetUpdateResult.Count; $i++) { $updateResult = $installResult.GetUpdateResult($i) $update = $updateCollection.Item($i) Write-WULog "Processing update $($i + 1): $($update.Title)" -Level Verbose Write-WULog "Update result code: $($updateResult.ResultCode)" -Level Verbose Write-WULog "Update HRESULT: $($updateResult.HResult)" -Level Verbose $updateStatus = @{ Title = $update.Title ResultCode = $updateResult.ResultCode HResult = $updateResult.HResult RestartRequired = $updateResult.RestartRequired } switch ($updateResult.ResultCode) { 1 { # In Progress Write-WULog "WARNING: Installation reports 'In Progress' for: $($update.Title) (This should not happen in synchronous mode)" -Level Warning $result.Errors += "Installation reports 'In Progress' - this indicates a potential issue with the update process" # Don't count as installed - this is an error condition } 2 { # Succeeded $result.UpdatesInstalled++ $result.InstalledUpdates += $updateStatus Write-WULog "Successfully installed: $($update.Title)" -Level Info } 3 { # Succeeded with errors $result.UpdatesInstalled++ $result.InstalledUpdates += $updateStatus Write-WULog "Installed with errors: $($update.Title)" -Level Warning } 4 { # Failed $result.UpdatesFailed++ $result.FailedUpdates += $updateStatus # Get user-friendly error description $errorDescription = Get-WUHResultDescription -HResult $updateResult.HResult Write-WULog "Failed to install: $($update.Title) - $errorDescription" -Level Error $result.Errors += "Failed to install: $($update.Title) - $errorDescription" } 5 { # Aborted $result.UpdatesFailed++ $result.FailedUpdates += $updateStatus Write-WULog "Installation aborted: $($update.Title)" -Level Error $result.Errors += "Installation aborted: $($update.Title)" } default { Write-WULog "Unknown result code $($updateResult.ResultCode) for: $($update.Title)" -Level Warning } } if ($updateResult.RestartRequired) { $result.RebootRequired = $true } } # Handle reboot requirement and set success status if ($result.RebootRequired) { if ($SuppressReboot) { Write-WULog "Updates installed successfully but a restart is required. Restart has been suppressed." -Level Warning } else { Write-WULog "Updates installed successfully. A restart is required and will be initiated." -Level Warning # Note: In a real implementation, you might want to integrate with your module's reboot handling # For now, we'll just log the requirement } } else { Write-WULog "Updates installed successfully. No restart required." -Level Info } # Set success status based on whether any updates were installed or if there were critical errors if ($result.UpdatesInstalled -gt 0 -or ($result.UpdatesFound -eq 0 -and $result.Errors.Count -eq 0)) { $result.Success = $true Write-WULog "Installation operation completed successfully" -Level Info } else { $result.Success = $false Write-WULog "Installation operation completed with errors - initiating automatic repair" -Level Warning # Automatic repair based on error patterns Write-Host "`n" + ("="*60) -ForegroundColor Yellow Write-Host " AUTOMATIC REPAIR INITIATED" -ForegroundColor Yellow Write-Host ("="*60) -ForegroundColor Yellow $errorString = $result.Errors -join " " $repairAttempted = $false if ($errorString -match "COMPONENT_STORE|0x80073712|0x800f0982|0x80070490") { Write-Host "Component store corruption detected - running component store repair..." -ForegroundColor Yellow try { $repairResult = Resolve-WUComponentStore -ForceSSU Write-Host "Component store repair completed with result: $($repairResult.OverallResult)" -ForegroundColor Green $result.Errors += "Auto-repair attempted: Component store repair - $($repairResult.OverallResult)" $repairAttempted = $true } catch { Write-Host "Component store repair failed: $($_.Exception.Message)" -ForegroundColor Red $result.Errors += "Auto-repair failed: Component store repair - $($_.Exception.Message)" } } elseif ($errorString -match "NOTDOWNLOADED|DOWNLOAD_FAILED|CONNECTION_ERROR|TIMEOUT") { Write-Host "Download/connectivity issues detected - running network troubleshooting..." -ForegroundColor Yellow try { $repairResult = Invoke-WUTroubleshooter Write-Host "Network troubleshooting completed" -ForegroundColor Green $result.Errors += "Auto-repair attempted: Network troubleshooting completed" $repairAttempted = $true } catch { Write-Host "Network troubleshooting failed: $($_.Exception.Message)" -ForegroundColor Red $result.Errors += "Auto-repair failed: Network troubleshooting - $($_.Exception.Message)" } } elseif ($errorString -match "INSTALL_NOT_ALLOWED|INSTALL_ALREADY_RUNNING") { Write-Host "Installation blocking detected - checking for pending operations..." -ForegroundColor Yellow try { $pendingUpdates = Get-WUPendingUpdates if ($pendingUpdates.PendingReboot) { Write-Host "Pending reboot detected - restart required before installation" -ForegroundColor Yellow $result.Errors += "Auto-diagnosis: Pending reboot detected - restart required" } else { Write-Host "No pending reboot found - may be another installer running" -ForegroundColor Yellow $result.Errors += "Auto-diagnosis: No pending reboot - check for other installers" } $repairAttempted = $true } catch { Write-Host "Pending operation check failed: $($_.Exception.Message)" -ForegroundColor Red $result.Errors += "Auto-diagnosis failed: Pending operation check - $($_.Exception.Message)" } } if (-not $repairAttempted) { Write-Host "Generic Windows Update failure - running comprehensive repair..." -ForegroundColor Yellow try { $repairResult = Invoke-WUComprehensiveRemediation Write-Host "Comprehensive repair completed with result: $($repairResult.OverallResult)" -ForegroundColor Green $result.Errors += "Auto-repair attempted: Comprehensive remediation - $($repairResult.OverallResult)" } catch { Write-Host "Comprehensive repair failed: $($_.Exception.Message)" -ForegroundColor Red $result.Errors += "Auto-repair failed: Comprehensive remediation - $($_.Exception.Message)" } } Write-Host ("="*60) -ForegroundColor Yellow Write-Host "" } } catch { if ($_.Exception.HResult -eq -2145124330) { # WU_E_INSTALL_NOT_ALLOWED $errorMsg = Get-WUHResultDescription -HResult $_.Exception.HResult Write-WULog $errorMsg -Level Error $result.Errors += $errorMsg } else { $errorDescription = Get-WUHResultDescription -HResult $_.Exception.HResult Write-WULog $errorDescription -Level Error $result.Errors += $errorDescription throw } } } } catch { # This is the top-level catch block. It ensures a friendly message is always logged. $errorMessage = if ($_) { if ($_.Exception -and $_.Exception.HResult -ne 0) { Get-WUHResultDescription -HResult $_.Exception.HResult } else { # If a string was thrown, use it directly. "Windows Update installation failed: $_" } } else { "An unknown error occurred during Windows Update installation." } Write-WULog $errorMessage -Level Error $result.Errors += $errorMessage # We do not re-throw here, allowing the 'finally' block to run and return the results object. } finally { # Restore original service state try { $currentService = Get-Service -Name "wuauserv" -ErrorAction SilentlyContinue if ($currentService -and $originalServiceState -eq "Stopped") { Write-WULog "Restoring Windows Update service to original state" -Level Info Set-Service -Name "wuauserv" -StartupType $originalStartType Stop-Service -Name "wuauserv" -Force -ErrorAction SilentlyContinue } } catch { Write-WULog "Warning: Could not restore original Windows Update service state: $($_.Exception.Message)" -Level Warning } # Clean up COM objects try { if ($updateInstaller) { [System.Runtime.Interopservices.Marshal]::ReleaseComObject($updateInstaller) | Out-Null } if ($updateDownloader) { [System.Runtime.Interopservices.Marshal]::ReleaseComObject($updateDownloader) | Out-Null } if ($updateSearcher) { [System.Runtime.Interopservices.Marshal]::ReleaseComObject($updateSearcher) | Out-Null } if ($updateSession) { [System.Runtime.Interopservices.Marshal]::ReleaseComObject($updateSession) | Out-Null } if ($updateCollection) { [System.Runtime.Interopservices.Marshal]::ReleaseComObject($updateCollection) | Out-Null } } catch { # Ignore COM cleanup errors } $stopwatch.Stop() $result.Duration = $stopwatch.Elapsed Write-WULog "Windows Update installation completed in $($result.Duration.ToString('hh\:mm\:ss'))" -Level Info Write-WULog "Summary - Success: $($result.Success), Found: $($result.UpdatesFound), Downloaded: $($result.UpdatesDownloaded), Installed: $($result.UpdatesInstalled), Failed: $($result.UpdatesFailed)" -Level Info # Format and display the final report Format-WUResult -Result $result } return $result } |