Private/Get-WUUpdateHistory.ps1
function Get-WUUpdateHistory { <# .SYNOPSIS Retrieves and analyzes Windows Update installation history. .DESCRIPTION Analyzes Windows Update history to identify patterns, success rates, and recent activity. Provides insights into update reliability. .PARAMETER Days Number of days back to analyze update history. Default is 30 days. .PARAMETER LogPath Path to the log file for detailed logging. .EXAMPLE $history = Get-WUUpdateHistory -Days 60 -LogPath "C:\Logs\wu.log" .NOTES This is a private function used internally by the WindowsUpdateTools module. Returns comprehensive update history analysis. #> [CmdletBinding()] param( [int]$Days = 30, [string]$LogPath ) Write-WULog -Message "Analyzing Windows Update history for last $Days days" -LogPath $LogPath # Initialize results object $results = [PSCustomObject]@{ AnalysisPeriod = $Days TotalUpdates = 0 SuccessfulUpdates = 0 FailedUpdates = 0 AbortedUpdates = 0 UpdatesWithErrors = 0 LastSuccessfulUpdate = $null LastFailedUpdate = $null DaysSinceLastSuccess = $null DaysSinceLastFailure = $null RecentUpdates = @() FailedUpdateDetails = @() UpdateFrequency = @{} CommonErrors = @() Issues = @() ErrorMessage = $null } try { Write-WULog -Message "Retrieving Windows Update history..." -LogPath $LogPath # Create Windows Update session $updateSession = New-Object -ComObject Microsoft.Update.Session $updateSearcher = $updateSession.CreateUpdateSearcher() # Get update history (limit to reasonable number to avoid performance issues) $historyCount = [math]::Min($Days * 5, 100) # Estimate 5 updates per day max, cap at 100 $updateHistory = $updateSearcher.QueryHistory(0, $historyCount) if ($updateHistory.Count -eq 0) { Write-WULog -Message "No update history found" -LogPath $LogPath return $results } Write-WULog -Message "Found $($updateHistory.Count) update history entries" -LogPath $LogPath $cutoffDate = (Get-Date).AddDays(-$Days) $recentHistory = @() # Process update history entries foreach ($entry in $updateHistory) { try { # Skip entries outside our analysis period if ($entry.Date -lt $cutoffDate) { continue } # Determine result status $resultText = switch ($entry.ResultCode) { 0 { "Not Started" } 1 { "In Progress" } 2 { "Succeeded" } 3 { "Succeeded with Errors" } 4 { "Failed" } 5 { "Aborted" } default { "Unknown ($($entry.ResultCode))" } } # Determine operation type $operationType = switch ($entry.Operation) { 1 { "Installation" } 2 { "Uninstallation" } 3 { "Other" } default { "Unknown" } } # Extract KB number if available $kbNumber = $null if ($entry.Title -match 'KB(\d+)') { $kbNumber = "KB$($matches[1])" } # Create history entry object $historyEntry = [PSCustomObject]@{ Date = $entry.Date Title = $entry.Title KBNumber = $kbNumber Operation = $operationType ResultCode = $entry.ResultCode Result = $resultText HResult = if ($entry.HResult) { "0x$($entry.HResult.ToString('X8'))" } else { $null } ClientApplicationID = $entry.ClientApplicationID ServerSelection = $entry.ServerSelection ServiceID = $entry.ServiceID Description = $entry.Description } $recentHistory += $historyEntry # Update counters $results.TotalUpdates++ switch ($entry.ResultCode) { 2 { $results.SuccessfulUpdates++ if (-not $results.LastSuccessfulUpdate -or $entry.Date -gt $results.LastSuccessfulUpdate) { $results.LastSuccessfulUpdate = $entry.Date } } 3 { $results.UpdatesWithErrors++ if (-not $results.LastSuccessfulUpdate -or $entry.Date -gt $results.LastSuccessfulUpdate) { $results.LastSuccessfulUpdate = $entry.Date # Still considered successful } } 4 { $results.FailedUpdates++ if (-not $results.LastFailedUpdate -or $entry.Date -gt $results.LastFailedUpdate) { $results.LastFailedUpdate = $entry.Date } # Add to failed update details $results.FailedUpdateDetails += [PSCustomObject]@{ Date = $entry.Date Title = $entry.Title KBNumber = $kbNumber HResult = $historyEntry.HResult Operation = $operationType } } 5 { $results.AbortedUpdates++ if (-not $results.LastFailedUpdate -or $entry.Date -gt $results.LastFailedUpdate) { $results.LastFailedUpdate = $entry.Date } } } # Track update frequency by month $monthKey = $entry.Date.ToString("yyyy-MM") if ($results.UpdateFrequency.ContainsKey($monthKey)) { $results.UpdateFrequency[$monthKey]++ } else { $results.UpdateFrequency[$monthKey] = 1 } } catch { Write-WULog -Message "Error processing history entry: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } } # Store recent updates (last 10) $results.RecentUpdates = $recentHistory | Sort-Object Date -Descending | Select-Object -First 10 # Calculate days since last success/failure if ($results.LastSuccessfulUpdate) { $results.DaysSinceLastSuccess = [math]::Round(((Get-Date) - $results.LastSuccessfulUpdate).TotalDays) } if ($results.LastFailedUpdate) { $results.DaysSinceLastFailure = [math]::Round(((Get-Date) - $results.LastFailedUpdate).TotalDays) } # Analyze common errors if ($results.FailedUpdateDetails.Count -gt 0) { $errorGroups = $results.FailedUpdateDetails | Where-Object { $_.HResult } | Group-Object HResult | Sort-Object Count -Descending $results.CommonErrors = $errorGroups | Select-Object -First 5 | ForEach-Object { [PSCustomObject]@{ ErrorCode = $_.Name Occurrences = $_.Count MostRecentFailure = ($_.Group | Sort-Object Date -Descending | Select-Object -First 1).Date Description = Get-WUErrorCodeDescription -ErrorCode $_.Name } } } # Generate issues based on analysis if ($results.TotalUpdates -eq 0) { $results.Issues += "No update activity found in the last $Days days" } if ($results.FailedUpdates -gt $results.SuccessfulUpdates -and $results.TotalUpdates -gt 0) { $results.Issues += "More failed updates ($($results.FailedUpdates)) than successful ones ($($results.SuccessfulUpdates))" } if ($results.DaysSinceLastSuccess -and $results.DaysSinceLastSuccess -gt 30) { $results.Issues += "Last successful update was $($results.DaysSinceLastSuccess) days ago" } if ($results.FailedUpdates -gt 5) { $results.Issues += "High number of failed updates ($($results.FailedUpdates)) indicates persistent issues" } if ($results.UpdatesWithErrors -gt 0) { $results.Issues += "$($results.UpdatesWithErrors) updates completed with errors" } # Calculate success rate $successRate = if ($results.TotalUpdates -gt 0) { [math]::Round((($results.SuccessfulUpdates + $results.UpdatesWithErrors) / $results.TotalUpdates) * 100, 1) } else { 0 } # Summary logging Write-WULog -Message "Update history analysis completed:" -LogPath $LogPath Write-WULog -Message " Total updates: $($results.TotalUpdates)" -LogPath $LogPath Write-WULog -Message " Successful: $($results.SuccessfulUpdates)" -LogPath $LogPath Write-WULog -Message " Failed: $($results.FailedUpdates)" -LogPath $LogPath Write-WULog -Message " Aborted: $($results.AbortedUpdates)" -LogPath $LogPath Write-WULog -Message " With errors: $($results.UpdatesWithErrors)" -LogPath $LogPath Write-WULog -Message " Success rate: $successRate%" -LogPath $LogPath if ($results.LastSuccessfulUpdate) { Write-WULog -Message " Last successful update: $($results.LastSuccessfulUpdate.ToString('yyyy-MM-dd HH:mm:ss')) ($($results.DaysSinceLastSuccess) days ago)" -LogPath $LogPath } else { Write-WULog -Message " No successful updates found in analysis period" -Level Warning -LogPath $LogPath } if ($results.CommonErrors.Count -gt 0) { Write-WULog -Message " Most common errors:" -LogPath $LogPath foreach ($error in $results.CommonErrors | Select-Object -First 3) { Write-WULog -Message " $($error.ErrorCode) ($($error.Occurrences)x): $($error.Description)" -LogPath $LogPath } } if ($results.Issues.Count -gt 0) { Write-WULog -Message "Update history issues:" -LogPath $LogPath foreach ($issue in $results.Issues) { Write-WULog -Message " - $issue" -Level Warning -LogPath $LogPath } } } catch { $results.ErrorMessage = $_.Exception.Message Write-WULog -Message "Error analyzing update history: $($_.Exception.Message)" -Level Error -LogPath $LogPath } return $results } |