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 DownloadOnly Download updates but do not install them. Updates will be left in a pending state ready for installation. Useful for pre-staging updates before a maintenance window. .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. .PARAMETER AutoRepair Automatically attempt to repair Windows Update issues if installation fails. When enabled, will run comprehensive remediation using existing module functions. .PARAMETER AnalyzeConfiguration Performs comprehensive Windows Update configuration analysis without installing updates. Shows system information, service status, COM object health, policy settings, and recommendations. .EXAMPLE Install-WindowsUpdate Installs all available critical software updates. .EXAMPLE Install-WindowsUpdate -IncludeDrivers -AcceptEula -AutoRepair Installs all available updates including drivers, automatically accepting any EULAs and enabling auto-repair. .EXAMPLE Install-WindowsUpdate -Criteria "IsInstalled=0 and Type='Software' and BrowseOnly=0" -SuppressReboot -AutoRepair Installs software updates with custom criteria, suppresses automatic reboot, and enables auto-repair on failure. .EXAMPLE Install-WindowsUpdate -DownloadOnly -AcceptEula Downloads all available updates without installing them, leaving them in pending state. .EXAMPLE Install-WindowsUpdate -DownloadPriority 2 -DisableForcedDownload Installs updates with normal BITS priority and standard throttling (for bandwidth-limited environments). .EXAMPLE Install-WindowsUpdate -AnalyzeConfiguration Performs a detailed analysis of the Windows Update environment and provides recommendations. .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.82 #> [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]$DownloadOnly, [Parameter()] [switch]$UseWindowsUpdate, [Parameter()] [ValidateRange(1, 4)] [int]$DownloadPriority = 4, [Parameter()] [switch]$DisableForcedDownload, [Parameter()] [int]$MaxRetries = 3, [Parameter()] [int]$RetryDelay = 30, [Parameter()] [switch]$AutoRepair, [Parameter()] [switch]$AnalyzeConfiguration ) $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 { # Helper function to get user-friendly HRESULT descriptions function Get-WUHResultDescription { param($HResult) # Convert to unsigned 32-bit for lookup $unsignedHResult = [UInt32]($HResult -band 0xFFFFFFFF) switch ($unsignedHResult) { 0x80240001 { "WU_E_NO_SERVICE - Windows Update Agent was unable to provide the service" } 0x80240002 { "WU_E_MAX_CAPACITY_REACHED - The maximum capacity of the service was exceeded" } 0x80240003 { "WU_E_UNKNOWN_ID - An ID cannot be found" } 0x00240003 { "WU_E_UNKNOWN_ID - An ID cannot be found (success variant)" } 0x80240004 { "WU_E_NOT_INITIALIZED - The object could not be initialized" } 0x80240005 { "WU_E_RANGEOVERLAP - The update handler requested a byte range overlapping a previously requested range" } 0x80240006 { "WU_E_TOOMANYRANGES - The requested number of byte ranges exceeds the maximum number" } 0x80240007 { "WU_E_INVALIDINDEX - The index to a collection was invalid" } 0x80240008 { "WU_E_ITEMNOTFOUND - The key for the item queried could not be found" } 0x80240009 { "WU_E_OPERATIONINPROGRESS - Another conflicting operation was in progress" } 0x8024000A { "WU_E_COULDNOTCANCEL - Cancellation of the operation was not allowed" } 0x8024000B { "WU_E_CALL_CANCELLED - Operation was cancelled" } 0x8024000C { "WU_E_NOUPDATE - Operation tried to add a duplicate item to a list" } 0x8024000D { "WU_E_INVALIDINSTALLATIONORDER - Could not perform a search for updates" } 0x8024000E { "WU_E_NOT_ALLOWED - Operation was not allowed" } 0x80240010 { "WU_E_DUPLICATE_ITEM - Operation tried to add a duplicate item to a list" } 0x80240011 { "WU_E_INVALID_INSTALL_REQUESTED - Cannot install update because it requires user input" } 0x80240012 { "WU_E_INSTALL_NOT_ALLOWED - Operation tried to install while another installation was in progress" } 0x80240013 { "WU_E_NOT_APPLICABLE - Operation was not performed because there are no applicable updates" } 0x80240016 { "WU_E_DOWNLOAD_FAILED - Operation failed because the download of the update failed" } 0x80240017 { "WU_E_INSTALL_FAILED - Installation failed for the update" } 0x80240018 { "WU_E_DOWNLOAD_NOT_ALLOWED - Operation tried to download when downloads are not allowed" } 0x80240019 { "WU_E_INVALID_UPDATE_TYPE - Operation was not allowed because the update contains invalid metadata" } 0x8024001A { "WU_E_URL_NOT_FOUND - Operation failed because the URL does not exist" } 0x8024001B { "WU_E_UNINSTALL_NOT_ALLOWED - Operation could not be completed because the update does not support uninstall" } 0x8024001C { "WU_E_INVALID_PRODUCT_LICENSE - Search may have missed some updates because the Windows Installer is less than version 3.1" } 0x8024001D { "WU_E_MISSING_HANDLER - A component required by Windows Update Agent was missing" } 0x8024001E { "WU_E_LEGACYSERVER - An operation was not completed because it requires a newer version of the server" } 0x8024001F { "WU_E_BIN_SOURCE_ABSENT - The update's binary source was absent" } 0x80240020 { "WU_E_SOURCE_ABSENT - The update's source was absent" } 0x80240021 { "WU_E_WU_DISABLED - Access to Windows Update was disabled" } 0x80240022 { "WU_E_CALL_CANCELLED_BY_POLICY - Operation was cancelled by Windows Update Agent policy" } 0x80240023 { "WU_E_INVALID_PROXY_SERVER - The proxy list was invalid" } 0x80240024 { "WU_E_INVALID_FILE - The file is in the wrong format" } 0x80240025 { "WU_E_INVALID_CRITERIA - The search criteria string was invalid" } 0x80240026 { "WU_E_INVALID_LICENSE - License terms could not be downloaded" } 0x80240027 { "WU_E_INVALID_SERIALNUMBER - The serial number is invalid" } 0x80240028 { "WU_E_INVALID_VOLUME_LABEL - The volume label is invalid" } 0x80240029 { "WU_E_INVALID_PRODUCT_LICENSE - Search may have missed some updates because the Windows Installer is less than version 3.1" } 0x8024002A { "WU_E_UPDATE_NOT_PROCESSED - The update was not processed" } 0x8024002B { "WU_E_INVALID_OPERATION_FOR_STATE - The operation cannot be performed on a Windows Update Agent if the agent is paused" } 0x8024002C { "WU_E_INVALID_INSTALL_REQUESTED - The install request is invalid" } 0x8024002D { "WU_E_INVALID_UNINSTALL_REQUESTED - The uninstall request is invalid" } 0x8024002E { "WU_E_SHUTDOWN_IN_PROGRESS - Agent is shutting down" } 0x8024002F { "WU_E_UNEXPECTED - An operation failed due to an unexpected condition" } 0x80240030 { "WU_E_NETWORK_NOT_AVAILABLE - A network error occurred" } 0x80240031 { "WU_E_MISSING_METADATA - The metadata for the update was not found" } 0x80240032 { "WU_E_METADATA_NOT_FOUND - The metadata for the update was not found" } 0x80240033 { "WU_E_TRANSPORT_ERROR - Agent was unable to download the update" } 0x80240034 { "WU_E_TIMEOUT - Operation timed out" } 0x80240035 { "WU_E_INVALID_FILE_FORMAT - The file has an invalid format" } 0x80240036 { "WU_E_INVALID_SIGNATURE - The signature of the file could not be verified" } 0x80240037 { "WU_E_INVALID_VOLUMEID - An invalid volume ID was encountered" } 0x80240038 { "WU_E_OUTOFRANGE - The data is out of range" } 0x80240039 { "WU_E_INVALIDWUAVERSION - The data contains a version that is not supported" } 0x8024003A { "WU_E_SEARCH_COMPLETED_WITH_SOME_FAILURES - The search completed successfully but some updates were skipped" } 0x8024003B { "WU_E_DOWNLOAD_COMPLETED_WITH_SOME_FAILURES - The download completed successfully but some updates failed to download" } 0x8024003C { "WU_E_WINHTTP_INVALID_FILE - The downloaded file has an unexpected content type" } 0x8024003D { "WU_E_INVALID_NOTIFICATION_INFO - Invalid notification information was provided" } 0x8024003E { "WU_E_INVALID_SERIALNUMBER_LICENSE - The serial number specified in the license is invalid" } 0x8024003F { "WU_E_INVALID_VOLUMEID_LICENSE - The volume ID specified in the license is invalid" } 0x80240040 { "WU_E_INVALID_PRODUCT_LICENSE_LICENSE - The product license is invalid" } 0x80240041 { "WU_E_INVALID_PRODUCT_LICENSE_OFFLINE - The product license is not valid for offline installation" } 0x80240042 { "WU_E_INVALID_PRODUCT_LICENSE_VERSION - The product license version is not supported" } 0x80240043 { "WU_E_INVALID_PRODUCT_LICENSE_CONTENTS - The product license contents are invalid" } 0x80240044 { "WU_E_INVALID_PRODUCT_LICENSE_MISSING - The product license is missing required information" } 0x80070005 { "E_ACCESSDENIED - Access is denied" } 0x8007000E { "E_OUTOFMEMORY - Ran out of memory" } 0x80072EE2 { "WININET_E_TIMEOUT - The operation timed out" } 0x80072EFD { "WININET_E_CANNOT_CONNECT - The attempt to connect to the server failed" } 0x80072F0D { "WININET_E_INVALID_CA - The function is unfamiliar with the Certificate Authority" } 0x80072F17 { "WININET_E_SEC_CERT_DATE_INVALID - SSL certificate date is invalid" } 0x80072F19 { "WININET_E_SEC_CERT_REV_FAILED - SSL certificate revocation check failed" } 0x80131501 { "Windows Update Agent COM corruption" } default { "Unknown error (0x$($unsignedHResult.ToString('X8')))" } } } # Configuration Analysis Mode if ($AnalyzeConfiguration.IsPresent) { Write-WULog "=== WINDOWS UPDATE CONFIGURATION ANALYSIS ===" -Level Info Write-WULog "Performing comprehensive pre-requisite checks..." -Level Info try { # 1. System Information Write-WULog "--- System Information ---" -Level Info $systemInfo = Get-WUSystemInfo Write-WULog "Operating System: $($systemInfo.OSVersion)" -Level Info Write-WULog "Architecture: $($systemInfo.Architecture)" -Level Info Write-WULog "Build Number: $($systemInfo.BuildNumber)" -Level Info Write-WULog "Edition: $($systemInfo.Edition)" -Level Info # 2. Windows Update Configuration Write-WULog "--- Windows Update Configuration ---" -Level Info $config = Get-WUConfiguration if ($config.WSUS) { Write-WULog "WSUS Server: $($config.WSUS.Server)" -Level Info Write-WULog "WSUS Port: $($config.WSUS.Port)" -Level Info Write-WULog "WSUS Status Port: $($config.WSUS.StatusServer)" -Level Info } else { Write-WULog "WSUS: Not configured (using Microsoft Update)" -Level Info } if ($config.GroupPolicy) { Write-WULog "Group Policy Settings:" -Level Info foreach ($policy in $config.GroupPolicy.GetEnumerator()) { Write-WULog " $($policy.Key): $($policy.Value)" -Level Info } } # 3. Service Health Check Write-WULog "--- Service Health Check ---" -Level Info $serviceResults = Test-WUServices -LogPath $logPath # Display individual service status foreach ($service in $serviceResults.Services) { $status = if ($service.Status -eq "Running") { "[OK]" } else { "[FAIL]" } Write-WULog "$status $($service.DisplayName) ($($service.Name)): $($service.Status) (Start Type: $($service.StartType))" -Level Info if ($service.Issues -and $service.Issues.Count -gt 0) { foreach ($issue in $service.Issues) { Write-WULog " Issue: $issue" -Level Warning } } } # 4. COM Object Testing Write-WULog "--- COM Object Testing ---" -Level Info $comTest = Test-WUCOMObject if ($comTest) { Write-WULog "[OK] Windows Update COM objects are functional" -Level Info } else { Write-WULog "[FAIL] COM Object Issues Detected" -Level Warning } # 5. System Health Assessment Write-WULog "--- System Health Assessment ---" -Level Info try { $health = Test-WUSystemHealth -LogPath $logPath if ($health.HealthScore) { Write-WULog "Overall Health Score: $($health.HealthScore)%" -Level Info } else { Write-WULog "Health assessment completed" -Level Info } if ($health.Issues -and $health.Issues.Count -gt 0) { Write-WULog "Issues Detected:" -Level Warning foreach ($issue in $health.Issues) { if ($issue -is [string]) { Write-WULog " [WARNING] $issue" -Level Warning } else { Write-WULog " [WARNING] $($issue.Category): $($issue.Description)" -Level Warning if ($issue.Recommendation) { Write-WULog " Recommendation: $($issue.Recommendation)" -Level Info } } } } else { Write-WULog "[OK] No issues detected" -Level Info } } catch { Write-WULog "System health assessment failed: $($_.Exception.Message)" -Level Error Write-WULog "Stack trace: $($_.ScriptStackTrace)" -Level Debug } # 6. Disk Space Check Write-WULog "--- Disk Space Analysis ---" -Level Info try { $systemDrive = Get-CimInstance -ClassName Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $env:SystemDrive } $freeSpaceGB = [math]::Round($systemDrive.FreeSpace / 1GB, 2) $totalSpaceGB = [math]::Round($systemDrive.Size / 1GB, 2) $usedPercentage = [math]::Round(($systemDrive.Size - $systemDrive.FreeSpace) / $systemDrive.Size * 100, 1) Write-WULog "System Drive ($($env:SystemDrive)): $freeSpaceGB GB free of $totalSpaceGB GB ($usedPercentage% used)" -Level Info if ($freeSpaceGB -lt 10) { Write-WULog "[WARNING] Low disk space detected - may impact update installation" -Level Warning } } catch { Write-WULog "Could not check disk space: $($_.Exception.Message)" -Level Warning } # 7. Network Connectivity Test Write-WULog "--- Network Connectivity Test ---" -Level Info try { $testUrls = @( "update.microsoft.com", "windowsupdate.microsoft.com", "download.microsoft.com" ) foreach ($url in $testUrls) { $result = Test-NetConnection -ComputerName $url -Port 443 -InformationLevel Quiet -ErrorAction SilentlyContinue $status = if ($result) { "[OK]" } else { "[FAIL]" } Write-WULog "$status $url connectivity" -Level Info } } catch { Write-WULog "Network connectivity test failed: $($_.Exception.Message)" -Level Warning } # 8. Pending Updates Check Write-WULog "--- Pending Updates Check ---" -Level Info try { $pending = Get-WUPendingUpdates if ($pending -and $pending.Count -gt 0) { Write-WULog "Found $($pending.Count) pending updates:" -Level Info foreach ($update in $pending) { $downloadStatus = if ($update.IsDownloaded) { "Downloaded" } else { "Not Downloaded" } Write-WULog " * $($update.Title) ($($update.SizeMB) MB) - $downloadStatus" -Level Info } } else { Write-WULog "[OK] No pending updates found" -Level Info } } catch { Write-WULog "Could not check pending updates: $($_.Exception.Message)" -Level Warning } # 9. Recent Update History Write-WULog "--- Recent Update History ---" -Level Info try { $history = Get-WUUpdateHistory | Select-Object -First 5 if ($history -and $history.Count -gt 0) { Write-WULog "Last 5 update operations:" -Level Info foreach ($item in $history) { Write-WULog " $($item.Date): $($item.Title) - $($item.Result)" -Level Info } } else { Write-WULog "No recent update history found" -Level Info } } catch { Write-WULog "Could not retrieve update history: $($_.Exception.Message)" -Level Warning } # 10. Recommendations Write-WULog "--- ANALYSIS COMPLETE ---" -Level Info Write-WULog "=== RECOMMENDATIONS ===" -Level Info $recommendations = @() # Service recommendations if ($services) { $stoppedServices = $services | Where-Object { $_.Status -ne "Running" } if ($stoppedServices -and $stoppedServices.Count -gt 0) { $recommendations += "Start stopped Windows Update services: $($stoppedServices.Name -join ', ')" } } # COM object recommendations if (-not $comTest) { $recommendations += "Repair Windows Update COM objects using: Invoke-WUComprehensiveRemediation" } # Health recommendations if ($health.HealthScore -and $health.HealthScore -lt 80) { $recommendations += "System health score is low ($($health.HealthScore)%) - consider running comprehensive remediation" } # Disk space recommendations if ($freeSpaceGB -and $freeSpaceGB -lt 10) { $recommendations += "Free up disk space before installing large updates" } # Policy recommendations if ($config.WSUS -and $config.WSUS.Server) { $recommendations += "Verify WSUS server accessibility if updates are failing" } if ($recommendations.Count -gt 0) { foreach ($rec in $recommendations) { Write-WULog "[RECOMMENDATION] $rec" -Level Info } } else { Write-WULog "[OK] System appears ready for Windows Update operations" -Level Info } Write-WULog "=== END ANALYSIS ===" -Level Info # Return analysis results and exit if only analyzing $analysisResult = @{ Success = $true AnalysisComplete = $true SystemInfo = $systemInfo Configuration = $config Services = $services COMObjects = $comTest SystemHealth = $health Recommendations = $recommendations } return $analysisResult } catch { Write-WULog "Configuration analysis failed: $($_.Exception.Message)" -Level Error Write-WULog "Stack trace: $($_.ScriptStackTrace)" -Level Verbose return @{ Success = $false AnalysisComplete = $false Error = $_.Exception.Message } } } 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 } # Check for Windows Update policies that might interfere Write-WULog "=== WINDOWS UPDATE POLICY ANALYSIS ===" -Level Verbose # Check Group Policy settings that commonly cause 0x80240022 $policyChecks = @{ "NoAutoUpdate" = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" "UseWUServer" = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" "WUServer" = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" "WUStatusServer" = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" "DisableWindowsUpdateAccess" = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" "ElevateNonAdmins" = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" } foreach ($policyName in $policyChecks.Keys) { $regPath = $policyChecks[$policyName] try { $value = Get-ItemProperty -Path $regPath -Name $policyName -ErrorAction SilentlyContinue if ($value) { Write-WULog "POLICY: $policyName = $($value.$policyName) (at $regPath)" -Level Warning # Specific policy warnings switch ($policyName) { "NoAutoUpdate" { if ($value.$policyName -eq 1) { Write-WULog "WARNING: Automatic Updates are disabled by Group Policy" -Level Warning } } "UseWUServer" { if ($value.$policyName -eq 1) { Write-WULog "WARNING: System is configured to use WSUS/internal update server" -Level Warning } } "DisableWindowsUpdateAccess" { if ($value.$policyName -eq 1) { Write-WULog "CRITICAL: Windows Update access is completely disabled by policy!" -Level Error } } } } } catch { # Policy not set, which is normal } } # Check if WSUS is configured try { $wsusServer = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name "WUServer" -ErrorAction SilentlyContinue if ($wsusServer) { Write-WULog "WSUS Server configured: $($wsusServer.WUServer)" -Level Info } } catch { } # Check Windows Update service configuration in registry try { $wuServiceConfig = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv" -ErrorAction SilentlyContinue if ($wuServiceConfig) { Write-WULog "Windows Update service Start type: $($wuServiceConfig.Start)" -Level Verbose Write-WULog "Windows Update service Image path: $($wuServiceConfig.ImagePath)" -Level Verbose } } catch { } Write-WULog "=== END POLICY ANALYSIS ===" -Level Verbose # 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 } # CRITICAL: Call Download() IMMEDIATELY like the working debug tool does # Any delay or additional COM interaction corrupts the object state Write-WULog "IMMEDIATE DOWNLOAD: Calling Download() method now (debug tool pattern)..." -Level Verbose try { $downloadResult = $updateDownloader.Download() Write-WULog "SUCCESS: Download() method completed!" -Level Info Write-WULog "Download ResultCode: $($downloadResult.ResultCode)" -Level Info # Detailed download result code interpretation $downloadResultCodeMeaning = switch ($downloadResult.ResultCode) { 0 { "Not Started" } 1 { "In Progress" } 2 { "Succeeded" } 3 { "Succeeded with Errors" } 4 { "Failed" } 5 { "Aborted" } default { "Unknown ($($downloadResult.ResultCode))" } } Write-WULog "Download result meaning: $downloadResultCodeMeaning" -Level Info # Check if we can access download HResult for failure details try { Write-WULog "Download HRESULT: 0x$($downloadResult.HResult.ToString('X8'))" -Level Verbose } catch { Write-WULog "Download HRESULT: Not available ($($_.Exception.Message))" -Level Verbose } # If download failed, get details about each update's download result if ($downloadResult.ResultCode -eq 4) { Write-WULog "=== DOWNLOAD FAILURE ANALYSIS ===" -Level Error for ($i = 0; $i -lt $downloadResult.GetUpdateResult.Count; $i++) { $updateDownloadResult = $downloadResult.GetUpdateResult($i) $update = $updateCollection.Item($i) Write-WULog "Update $($i + 1): $($update.Title)" -Level Error Write-WULog " Download Result Code: $($updateDownloadResult.ResultCode)" -Level Error Write-WULog " Download HRESULT: 0x$($updateDownloadResult.HResult.ToString('X8'))" -Level Error # Get user-friendly error description for download failure $downloadErrorDescription = Get-WUHResultDescription -HResult $updateDownloadResult.HResult Write-WULog " Download Error: $downloadErrorDescription" -Level Error } } # If download succeeded with errors, analyze which updates had issues if ($downloadResult.ResultCode -eq 3) { Write-WULog "=== DOWNLOAD SUCCEEDED WITH ERRORS ANALYSIS ===" -Level Warning for ($i = 0; $i -lt $downloadResult.GetUpdateResult.Count; $i++) { $updateDownloadResult = $downloadResult.GetUpdateResult($i) $update = $updateCollection.Item($i) $downloadResultMeaning = switch ($updateDownloadResult.ResultCode) { 0 { "Not Started" } 1 { "In Progress" } 2 { "Succeeded" } 3 { "Succeeded with Errors" } 4 { "Failed" } 5 { "Aborted" } default { "Unknown ($($updateDownloadResult.ResultCode))" } } Write-WULog "Update $($i + 1): $($update.Title)" -Level Warning Write-WULog " Individual Download Result: $($updateDownloadResult.ResultCode) ($downloadResultMeaning)" -Level Warning Write-WULog " Individual Download HRESULT: 0x$($updateDownloadResult.HResult.ToString('X8'))" -Level Warning Write-WULog " Is Downloaded: $($update.IsDownloaded)" -Level Warning if ($updateDownloadResult.HResult -ne 0) { $downloadErrorDescription = Get-WUHResultDescription -HResult $updateDownloadResult.HResult Write-WULog " Download Issue: $downloadErrorDescription" -Level Warning } } } } catch { $hresult = [System.String]::Format("0x{0:X8}", $_.Exception.HResult) Write-WULog "ERROR: Download failed with HResult: $hresult" -Level Error Write-WULog "ERROR: Exception: $($_.Exception.Message)" -Level Error throw "Download failed: $($_.Exception.Message)" } $result.UpdatesDownloaded = $updateCollection.Count # If DownloadOnly is specified, skip installation if ($DownloadOnly) { Write-WULog "DownloadOnly mode: Skipping installation. $($updateCollection.Count) update(s) downloaded and pending." -Level Info $result.Success = $true # Report download sizes $totalDownloadedMB = 0 for ($i = 0; $i -lt $updateCollection.Count; $i++) { $update = $updateCollection.Item($i) $sizeMB = [math]::Round($update.MaxDownloadSize / 1MB, 2) $totalDownloadedMB += $sizeMB Write-WULog " Downloaded: $($update.Title) ($sizeMB MB)" -Level Info } Write-WULog "Total downloaded: $([math]::Round($totalDownloadedMB, 2)) MB" -Level Info Write-WULog "Updates are now in pending state and ready for installation" -Level Info $stopwatch.Stop() $result.Duration = $stopwatch.Elapsed return $result } # Install updates if ($PSCmdlet.ShouldProcess("$($updateCollection.Count) Windows Updates", "Install")) { # Skip download validation if we just completed a successful download # The IsDownloaded property can be unreliable immediately after download completion if ($downloadResult.ResultCode -eq 2) { Write-WULog "Skipping download validation - download completed successfully" -Level Verbose } elseif ($downloadResult.ResultCode -eq 3) { Write-WULog "Download completed with errors - validating individual update download status..." -Level Warning $notDownloaded = @() for ($i = 0; $i -lt $updateCollection.Count; $i++) { $update = $updateCollection.Item($i) if (-not $update.IsDownloaded) { $notDownloaded += $update.Title Write-WULog "Update not downloaded: $($update.Title)" -Level Warning } else { Write-WULog "Update successfully downloaded: $($update.Title)" -Level Verbose } } 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 } } else { # Only validate if download didn't complete successfully Write-WULog "Validating download status before installation..." -Level Verbose $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 { # Pre-installation debugging Write-WULog "=== PRE-INSTALLATION DEBUG ===" -Level Verbose Write-WULog "DEBUG: Update installer object type: $($updateInstaller.GetType().FullName)" -Level Verbose Write-WULog "DEBUG: Update collection count: $($updateCollection.Count)" -Level Verbose Write-WULog "DEBUG: Updates to install: $($updateCollection.Count)" -Level Verbose # Check each update's state before installation for ($i = 0; $i -lt $updateCollection.Count; $i++) { $update = $updateCollection.Item($i) Write-WULog "DEBUG: Pre-install Update $($i + 1): $($update.Title)" -Level Verbose Write-WULog "DEBUG: IsDownloaded: $($update.IsDownloaded)" -Level Verbose Write-WULog "DEBUG: IsInstalled: $($update.IsInstalled)" -Level Verbose Write-WULog "DEBUG: Size: $([math]::Round($update.MaxDownloadSize / 1MB, 2)) MB" -Level Verbose Write-WULog "DEBUG: IsMandatory: $($update.IsMandatory)" -Level Verbose Write-WULog "DEBUG: RebootRequired: $($update.RebootRequired)" -Level Verbose Write-WULog "DEBUG: IsHidden: $($update.IsHidden)" -Level Verbose Write-WULog "DEBUG: IsUninstallable: $($update.IsUninstallable)" -Level Verbose if ($update.BundledUpdates.Count -gt 0) { Write-WULog "DEBUG: Bundled updates: $($update.BundledUpdates.Count)" -Level Verbose } } # Check installer properties Write-WULog "DEBUG: Installer AllowSourcePrompts: $($updateInstaller.AllowSourcePrompts)" -Level Verbose Write-WULog "DEBUG: Installer ClientApplicationID: $($updateInstaller.ClientApplicationID)" -Level Verbose Write-WULog "DEBUG: Installer ForceQuiet: $($updateInstaller.ForceQuiet)" -Level Verbose Write-WULog "DEBUG: Installer IsBusy: $($updateInstaller.IsBusy)" -Level Verbose Write-WULog "DEBUG: Installer ParentHwnd: $($updateInstaller.ParentHwnd)" -Level Verbose Write-WULog "DEBUG: Installer ParentWindow: $($updateInstaller.ParentWindow)" -Level Verbose Write-WULog "=== STARTING INSTALLATION ===" -Level Verbose # 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) Write-WULog "DEBUG: Calling updateInstaller.Install() now..." -Level Verbose $installResult = $updateInstaller.Install() Write-WULog "DEBUG: updateInstaller.Install() returned" -Level Verbose # Check if installation got stuck in "In Progress" state if ($installResult.ResultCode -eq 1) { Write-WULog "WARNING: Installation returned 'In Progress' status - this may indicate a stuck installation" -Level Warning Write-WULog "DEBUG: Waiting 30 seconds to see if installation completes..." -Level Verbose Start-Sleep -Seconds 30 # Check if it's still in progress after waiting if ($installResult.ResultCode -eq 1) { Write-WULog "ERROR: Installation appears to be stuck in 'In Progress' state after 30 seconds" -Level Error Write-WULog "This often indicates policy interference or Windows Update Agent corruption" -Level Error } } 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 "=== POST-INSTALLATION DEBUG ===" -Level Verbose Write-WULog "Installation result object type: $($installResult.GetType().FullName)" -Level Verbose Write-WULog "Installation result code: $($installResult.ResultCode)" -Level Verbose # Detailed result code interpretation $installResultCodeMeaning = switch ($installResult.ResultCode) { 0 { "Not Started" } 1 { "In Progress" } 2 { "Succeeded" } 3 { "Succeeded with Errors" } 4 { "Failed" } 5 { "Aborted" } default { "Unknown ($($installResult.ResultCode))" } } Write-WULog "Installation result meaning: $installResultCodeMeaning" -Level Verbose # Special handling for policy cancellation error if ($installResult.ResultCode -eq 4 -and $installResult.HResult -eq -2145124318) { Write-WULog "=== POLICY CANCELLATION DETECTED ===" -Level Error Write-WULog "Installation was cancelled by Windows Update policy (0x80240022)" -Level Error Write-WULog "This typically means:" -Level Error Write-WULog " 1. Group Policy is blocking Windows Update installation" -Level Error Write-WULog " 2. WSUS is configured but not accessible" -Level Error Write-WULog " 3. Windows Update service policies are restricting operations" -Level Error Write-WULog " 4. Third-party software is interfering with Windows Update" -Level Error Write-WULog "Check the policy analysis above for specific policy conflicts" -Level Error } Write-WULog "Number of update results: $($installResult.GetUpdateResult.Count)" -Level Verbose Write-WULog "Reboot required: $($installResult.RebootRequired)" -Level Verbose # Check if we can access HResult try { Write-WULog "Installation HRESULT: 0x$($installResult.HResult.ToString('X8'))" -Level Verbose } catch { Write-WULog "Installation HRESULT: Not available ($($_.Exception.Message))" -Level Verbose } Write-WULog "=== PROCESSING UPDATE RESULTS ===" -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 # Detailed update result code interpretation $updateResultCodeMeaning = switch ($updateResult.ResultCode) { 0 { "Not Started" } 1 { "In Progress" } 2 { "Succeeded" } 3 { "Succeeded with Errors" } 4 { "Failed" } 5 { "Aborted" } default { "Unknown ($($updateResult.ResultCode))" } } Write-WULog "Update result meaning: $updateResultCodeMeaning" -Level Verbose Write-WULog "Update HRESULT: 0x$($updateResult.HResult.ToString('X8'))" -Level Verbose Write-WULog "Update restart required: $($updateResult.RestartRequired)" -Level Verbose # Check post-installation state Write-WULog "DEBUG: Post-install IsDownloaded: $($update.IsDownloaded)" -Level Verbose Write-WULog "DEBUG: Post-install IsInstalled: $($update.IsInstalled)" -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)" -Level Warning Write-WULog "DEBUG: This should not happen in synchronous mode - indicates a stuck installation" -Level Warning Write-WULog "DEBUG: Overall installation HRESULT was: 0x$($installResult.HResult.ToString('X8'))" -Level Warning Write-WULog "DEBUG: Update IsDownloaded after install attempt: $($update.IsDownloaded)" -Level Warning Write-WULog "DEBUG: Update IsInstalled after install attempt: $($update.IsInstalled)" -Level Warning # Check if this correlates with policy cancellation if ($installResult.HResult -eq -2145124318) { Write-WULog "ANALYSIS: 'In Progress' state caused by policy cancellation (0x80240022)" -Level Error Write-WULog "The installation was cancelled by Group Policy before it could complete" -Level Error } else { Write-WULog "ANALYSIS: 'In Progress' state with unexpected overall HRESULT" -Level Warning Write-WULog "This may indicate Windows Update Agent corruption or service issues" -Level Warning } $result.Errors += "Installation stuck in 'In Progress' state for: $($update.Title) - likely policy interference or corruption" # Don't count as installed - this is an error condition } 2 { # Succeeded # CRITICAL VALIDATION: Verify the update is actually installed # Some installations report "Succeeded" but don't actually install if ($update.IsInstalled -eq $true) { $result.UpdatesInstalled++ $result.InstalledUpdates += $updateStatus Write-WULog "Successfully installed: $($update.Title)" -Level Info } else { # FAKE SUCCESS DETECTION: Installation claimed success but update not installed Write-WULog "CRITICAL: Installation reported SUCCESS but update is NOT INSTALLED!" -Level Error Write-WULog "Title: $($update.Title)" -Level Error Write-WULog "ResultCode: $($updateResult.ResultCode) (Succeeded)" -Level Error Write-WULog "IsInstalled: $($update.IsInstalled) (FALSE - should be TRUE)" -Level Error Write-WULog "HRESULT: 0x$($updateResult.HResult.ToString('X8'))" -Level Error # Additional diagnostics for fake success Write-WULog "DIAGNOSIS: This is a 'fake success' - possible causes:" -Level Warning Write-WULog " 1. Windows Update Agent corruption" -Level Warning Write-WULog " 2. Policy interference after installation started" -Level Warning Write-WULog " 3. Insufficient permissions or service issues" -Level Warning Write-WULog " 4. Component store corruption preventing final commit" -Level Warning # Check for specific error patterns if ($updateResult.HResult -ne 0) { Write-WULog "Non-zero HRESULT suggests underlying issue: 0x$($updateResult.HResult.ToString('X8'))" -Level Error } $result.UpdatesFailed++ $result.FailedUpdates += $updateStatus $result.Errors += "FAKE SUCCESS: $($update.Title) - Installation reported success but update not installed (possible corruption or policy interference)" # Mark overall operation as failed due to fake success $result.Success = $false } } 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 Write-WULog "DEBUG: This is result code 0 - indicating installation never started or was aborted" -Level Warning Write-WULog "DEBUG: HRESULT details: 0x$($updateResult.HResult.ToString('X8'))" -Level Warning Write-WULog "DEBUG: This suggests a fundamental issue preventing installation from starting" -Level Warning # Treat as failed $result.UpdatesFailed++ $result.FailedUpdates += $updateStatus $result.Errors += "Installation failed to start for: $($update.Title) (Result code $($updateResult.ResultCode), HRESULT: 0x$($updateResult.HResult.ToString('X8')))" } } 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 } # CRITICAL: Final validation - detect any remaining fake successes Write-WULog "=== FINAL INSTALLATION VALIDATION ===" -Level Verbose Write-WULog "Performing post-installation verification..." -Level Info $actuallyInstalled = 0 $fakeSuccesses = 0 foreach ($updateInfo in $updatesToProcess) { $originalUpdate = $updateInfo.Update try { # Re-check installation status after claimed success $isActuallyInstalled = $originalUpdate.IsInstalled Write-WULog "Final check - $($originalUpdate.Title): IsInstalled = $isActuallyInstalled" -Level Verbose if ($isActuallyInstalled) { $actuallyInstalled++ # Microsoft-recommended pattern: Verify installation depth # Check if bundled updates were also properly installed if ($originalUpdate.BundledUpdates.Count -gt 0) { Write-WULog "Verifying $($originalUpdate.BundledUpdates.Count) bundled updates for: $($originalUpdate.Title)" -Level Verbose $bundledFailed = 0 for ($b = 0; $b -lt $originalUpdate.BundledUpdates.Count; $b++) { $bundledUpdate = $originalUpdate.BundledUpdates.Item($b) if (-not $bundledUpdate.IsInstalled) { $bundledFailed++ Write-WULog "Bundled update not installed: $($bundledUpdate.Title)" -Level Warning } } if ($bundledFailed -gt 0) { Write-WULog "WARNING: Main update installed but $bundledFailed bundled updates failed" -Level Warning } } } else { $fakeSuccesses++ Write-WULog "CONFIRMED FAKE SUCCESS: $($originalUpdate.Title) - not actually installed despite success report" -Level Error # Microsoft-pattern: Additional validation for complex updates # Check if this is a superseded update (common cause of fake success) try { if ($originalUpdate.IsSuperseded) { Write-WULog "ANALYSIS: Update was superseded during installation - this may explain the fake success" -Level Warning } } catch { } } } catch { Write-WULog "Could not verify final installation status for: $($originalUpdate.Title) - $($_.Exception.Message)" -Level Warning # Microsoft-pattern: Attempt alternative verification try { # Re-search for the update to see if it's still available (indicating not installed) Write-WULog "Attempting alternative verification via re-search..." -Level Verbose $verificationSearcher = $updateSession.CreateUpdateSearcher() $verificationCriteria = "UpdateID='$($originalUpdate.Identity.UpdateID)' and IsInstalled=0" $verificationResult = $verificationSearcher.Search($verificationCriteria) if ($verificationResult.Updates.Count -gt 0) { Write-WULog "Alternative verification: Update still appears as not installed" -Level Warning $fakeSuccesses++ } else { Write-WULog "Alternative verification: Update no longer appears in not-installed search" -Level Verbose $actuallyInstalled++ } } catch { Write-WULog "Alternative verification failed: $($_.Exception.Message)" -Level Verbose } } } Write-WULog "Installation validation complete:" -Level Info Write-WULog " Updates actually installed: $actuallyInstalled" -Level Info Write-WULog " Fake successes detected: $fakeSuccesses" -Level Info if ($fakeSuccesses -gt 0) { Write-WULog "CRITICAL: $fakeSuccesses fake success(es) detected!" -Level Error Write-WULog "This indicates Windows Update API integrity issues." -Level Error Write-WULog "" -Level Info Write-WULog "Microsoft-recommended remediation steps:" -Level Warning Write-WULog " 1. Policy Analysis: Check for Group Policy conflicts blocking final installation commit" -Level Warning Write-WULog " 2. Component Store: Run 'dism /online /cleanup-image /restorehealth' as Administrator" -Level Warning Write-WULog " 3. Update Agent: Reset Windows Update components using 'Repair-WindowsUpdate'" -Level Warning Write-WULog " 4. Service Health: Verify Windows Update service and dependencies are functioning" -Level Warning Write-WULog " 5. Registry Integrity: Check for corrupted Windows Update registry keys" -Level Warning Write-WULog " 6. Event Logs: Review Windows Update logs in Event Viewer for detailed errors" -Level Warning Write-WULog "" -Level Info Write-WULog "Based on Microsoft documentation patterns, fake successes commonly indicate:" -Level Info Write-WULog " - Group Policy preventing final installation commit after download/prepare phases" -Level Info Write-WULog " - Windows Update Agent COM object corruption requiring service reset" -Level Info Write-WULog " - Component Store corruption preventing update registration" -Level Info Write-WULog " - Insufficient system resources during final installation phase" -Level Info # Check if this appears to be Group Policy interference $groupPolicyDetected = $false try { $auPolicy = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoUpdate" -ErrorAction SilentlyContinue if ($auPolicy -and $auPolicy.NoAutoUpdate -eq 1) { $groupPolicyDetected = $true Write-WULog "" -Level Info Write-WULog "GROUP POLICY INTERFERENCE DETECTED!" -Level Warning Write-WULog "Automatic updates are disabled via Group Policy, which can cause fake successes." -Level Warning Write-WULog "The installation API can download and prepare updates but policy blocks final commit." -Level Warning Write-WULog "" -Level Info Write-WULog "SOLUTION: Use 'Repair-WindowsUpdate -GroupPolicy' to temporarily allow installations:" -Level Info Write-WULog " 1. Repair-WindowsUpdate -GroupPolicy # Temporarily allow updates and create backup" -Level Info Write-WULog " 2. Install-WindowsUpdate # Install updates" -Level Info Write-WULog " 3. reg import <backup_file> # Restore policies (path shown in repair output)" -Level Info } } catch { } # Override success status if fake successes detected (Microsoft pattern: strict validation) if ($result.Success -and $fakeSuccesses -gt 0) { $result.Success = $false Write-WULog "Installation marked as FAILED due to fake success detection (following Microsoft validation standards)" -Level Error # Add specific remediation suggestion to result if ($groupPolicyDetected) { $result.Errors += "FAKE SUCCESS due to Group Policy interference - Use 'Repair-WindowsUpdate -GroupPolicy' to temporarily allow installations" } else { $result.Errors += "FAKE SUCCESS detected - Installation API reported success but updates not actually installed" } } } else { Write-WULog "Installation validation successful: All claimed successes verified as genuine" -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 { # Check if "In Progress" errors are due to pending reboot (common and normal) $inProgressErrors = $result.Errors | Where-Object { $_ -like "*In Progress*" } $hasRebootRequired = $result.RebootRequired -or $installResult.RebootRequired if ($inProgressErrors.Count -gt 0 -and $hasRebootRequired) { Write-WULog "Installation shows 'In Progress' but reboot is required - this is normal, not an error" -Level Info $result.Success = $true Write-WULog "Installation operation completed successfully (pending reboot)" -Level Info } else { $result.Success = $false if ($AutoRepair) { Write-WULog "Installation operation completed with errors - initiating automatic repair" -Level Warning $repairResults = Invoke-WUAutoRepair -Errors $result.Errors $result.Errors += $repairResults } else { Write-WULog "Installation operation completed with errors. Use -AutoRepair to attempt automatic remediation." -Level Warning } } } } catch { Write-WULog "=== INSTALLATION EXCEPTION CAUGHT ===" -Level Error Write-WULog "Exception type: $($_.Exception.GetType().FullName)" -Level Error Write-WULog "Exception message: $($_.Exception.Message)" -Level Error Write-WULog "Exception HRESULT: 0x$($_.Exception.HResult.ToString('X8'))" -Level Error Write-WULog "Exception at: $($_.InvocationInfo.ScriptLineNumber):$($_.InvocationInfo.OffsetInLine)" -Level Error 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() # Handle Duration property based on result type if ($result -is [hashtable] -and $result.ContainsKey('Duration')) { $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 for normal installation mode Format-WUResult -Result $result } else { # Analysis mode - result has different structure $duration = $stopwatch.Elapsed Write-WULog "Configuration analysis completed in $($duration.ToString('hh\:mm\:ss'))" -Level Info # Don't call Format-WUResult for analysis results as it expects different structure if ($result.Success) { Write-WULog "=== Analysis completed successfully ===" -Level Info } else { Write-WULog "=== Analysis completed with errors ===" -Level Warning } } } return $result } |