Private/Get-WUEventLogs.ps1
function Get-WUEventLogs { <# .SYNOPSIS Analyzes Windows Update related events from system event logs. .DESCRIPTION Comprehensive analysis of Windows Update events from multiple log sources including Windows Update Client operational log, System log, and Application log. Identifies errors, patterns, and provides actionable insights. .PARAMETER Days Number of days back to analyze events. Default is 7 days. .PARAMETER LogPath Path to the log file for detailed logging. .EXAMPLE $eventResults = Get-WUEventLogs -Days 14 -LogPath "C:\Logs\wu.log" .NOTES This is a private function used internally by the WindowsUpdateTools module. Returns detailed analysis of Windows Update related events. #> [CmdletBinding()] param( [int]$Days = 7, [string]$LogPath ) Write-WULog -Message "Analyzing Windows Update events from last $Days days" -LogPath $LogPath # Initialize results object $results = [PSCustomObject]@{ AnalysisPeriod = $Days EventSources = @() TotalEvents = 0 CriticalErrors = 0 ErrorEvents = 0 WarningEvents = 0 SignificantErrorCodes = @() CommonFailures = @() RecentFailures = @() UpdateAttempts = 0 SuccessfulUpdates = 0 FailedUpdates = 0 LastSuccessfulUpdate = $null LastFailedUpdate = $null Issues = @() } $startTime = (Get-Date).AddDays(-$Days) try { # Define event log sources to analyze $logSources = @( @{ LogName = 'Microsoft-Windows-WindowsUpdateClient/Operational' Description = 'Windows Update Client Operations' Critical = $true Optional = $false }, @{ LogName = 'System' Description = 'System Events' Critical = $true Optional = $false }, @{ LogName = 'Application' Description = 'Application Events' Critical = $false Optional = $false } ) $allRelevantEvents = @() foreach ($logSource in $logSources) { Write-WULog -Message "Analyzing log: $($logSource.LogName)" -LogPath $LogPath $sourceResult = [PSCustomObject]@{ LogName = $logSource.LogName Description = $logSource.Description EventsFound = 0 ErrorEvents = 0 CriticalEvents = 0 WarningEvents = 0 Available = $false ErrorMessage = $null } try { # First check if the log exists $logExists = Get-WinEvent -ListLog $logSource.LogName -ErrorAction SilentlyContinue if (-not $logExists) { if ($logSource.Optional) { Write-WULog -Message " Optional log $($logSource.LogName) not available on this system" -LogPath $LogPath } else { Write-WULog -Message " Log $($logSource.LogName) does not exist on this system" -Level Warning -LogPath $LogPath } $sourceResult.Available = $false $sourceResult.ErrorMessage = "Log does not exist on this system" $results.EventSources += $sourceResult continue } # Attempt to read events from this log $filterParams = @{ LogName = $logSource.LogName Level = 1..3 # Critical, Error, Warning StartTime = $startTime } $events = Get-WinEvent -FilterHashtable $filterParams -MaxEvents 100 -ErrorAction Stop # Filter for Windows Update related events (more specific) $relevantEvents = $events | Where-Object { ( $_.ProviderName -like "*WindowsUpdate*" -or $_.ProviderName -like "*BITS*" -or $_.ProviderName -eq "Microsoft-Windows-WindowsUpdateClient" ) -and ( $_.Message -like "*update*" -or $_.Message -like "*install*" ) } $sourceResult.EventsFound = $relevantEvents.Count $sourceResult.Available = $true # Categorize events by level $sourceResult.CriticalEvents = ($relevantEvents | Where-Object { $_.Level -eq 1 }).Count $sourceResult.ErrorEvents = ($relevantEvents | Where-Object { $_.Level -eq 2 }).Count $sourceResult.WarningEvents = ($relevantEvents | Where-Object { $_.Level -eq 3 }).Count $results.CriticalErrors += $sourceResult.CriticalEvents $results.ErrorEvents += $sourceResult.ErrorEvents $results.WarningEvents += $sourceResult.WarningEvents # Add to overall collection $allRelevantEvents += $relevantEvents Write-WULog -Message " Found $($sourceResult.EventsFound) relevant events ($($sourceResult.CriticalEvents) critical, $($sourceResult.ErrorEvents) errors)" -LogPath $LogPath } catch { if ($_.Exception.Message -like '*No events were found*') { Write-WULog -Message " No relevant events found in $($logSource.LogName)" -LogPath $LogPath $sourceResult.Available = $true } elseif ($_.Exception.Message -like '*There is not an event log*' -or $_.Exception.Message -like '*does not exist*') { if ($logSource.Optional) { Write-WULog -Message " Optional log $($logSource.LogName) not available on this system" -LogPath $LogPath } else { Write-WULog -Message " Log $($logSource.LogName) does not exist on this system" -Level Warning -LogPath $LogPath } $sourceResult.Available = $false $sourceResult.ErrorMessage = "Log does not exist" } elseif ($_.Exception.Message -like '*Access*denied*' -or $_.Exception.Message -like '*permission*') { Write-WULog -Message " Access denied to $($logSource.LogName) - try running as Administrator" -Level Warning -LogPath $LogPath $sourceResult.Available = $false $sourceResult.ErrorMessage = "Access denied" } else { Write-WULog -Message " Error accessing $($logSource.LogName): $($_.Exception.Message)" -Level Warning -LogPath $LogPath $sourceResult.ErrorMessage = $_.Exception.Message $sourceResult.Available = $false } } $results.EventSources += $sourceResult } $results.TotalEvents = $allRelevantEvents.Count if ($allRelevantEvents.Count -gt 0) { Write-WULog -Message "Analyzing $($allRelevantEvents.Count) relevant events for patterns..." -LogPath $LogPath # Extract error codes and patterns $errorCodes = @() $updateAttempts = @() $failures = @() foreach ($event in $allRelevantEvents) { # Extract error codes from messages $errorMatches = [regex]::Matches($event.Message, '0x[0-9A-Fa-f]{8}') foreach ($match in $errorMatches) { $errorCodes += $match.Value } # Look for specific Windows Update patterns if ($event.Message -like "*failed*" -or $event.Message -like "*error*") { $failures += [PSCustomObject]@{ TimeCreated = $event.TimeCreated Level = $event.LevelDisplayName Source = $event.ProviderName EventId = $event.Id Message = $event.Message -replace "`r`n", ' ' -replace ' +', ' ' ErrorCodes = ($errorMatches | ForEach-Object { $_.Value }) -join ', ' } } # Track update attempts if ($event.Id -in @(19, 20, 43) -or $event.Message -like "*installation*" -or $event.Message -like "*download*") { $updateAttempts += $event } } # Analyze error codes if ($errorCodes.Count -gt 0) { $uniqueErrorCodes = $errorCodes | Group-Object | Sort-Object Count -Descending $results.SignificantErrorCodes = $uniqueErrorCodes | ForEach-Object { [PSCustomObject]@{ ErrorCode = $_.Name Occurrences = $_.Count Description = Get-WUErrorCodeDescription -ErrorCode $_.Name } } $errorCodeSummary = ($uniqueErrorCodes[0..4] | ForEach-Object { "$($_.Name) ($($_.Count)x)" }) -join ', ' Write-WULog -Message "Found error codes: $errorCodeSummary" -LogPath $LogPath } # Analyze recent failures (last 48 hours) $recentFailures = $failures | Where-Object { $_.TimeCreated -gt (Get-Date).AddHours(-48) } | Sort-Object TimeCreated -Descending $results.RecentFailures = $recentFailures | Select-Object -First 10 # Track update success/failure rates $results.UpdateAttempts = $updateAttempts.Count # Look for successful updates $successEvents = $allRelevantEvents | Where-Object { $_.Message -like "*successfully*" -and ($_.Message -like "*install*" -or $_.Message -like "*update*") } $results.SuccessfulUpdates = $successEvents.Count if ($successEvents.Count -gt 0) { $results.LastSuccessfulUpdate = ($successEvents | Sort-Object TimeCreated -Descending | Select-Object -First 1).TimeCreated } # Count failed updates $failedUpdateEvents = $failures | Where-Object { $_.Message -like "*install*" -or $_.Message -like "*update*" } $results.FailedUpdates = $failedUpdateEvents.Count if ($failedUpdateEvents.Count -gt 0) { $results.LastFailedUpdate = ($failedUpdateEvents | Sort-Object TimeCreated -Descending | Select-Object -First 1).TimeCreated } # Identify common failure patterns $commonFailures = $failures | Group-Object { # Group by similar error messages (first 100 characters) if ($_.Message.Length -gt 100) { $_.Message.Substring(0, 100) } else { $_.Message } } | Where-Object { $_.Count -gt 1 } | Sort-Object Count -Descending $results.CommonFailures = $commonFailures | Select-Object -First 5 | ForEach-Object { [PSCustomObject]@{ Pattern = $_.Name Occurrences = $_.Count FirstOccurrence = ($_.Group | Sort-Object TimeCreated | Select-Object -First 1).TimeCreated LastOccurrence = ($_.Group | Sort-Object TimeCreated -Descending | Select-Object -First 1).TimeCreated } } # Generate issues based on analysis if ($results.CriticalErrors -gt 0) { $results.Issues += "Found $($results.CriticalErrors) critical errors in Windows Update logs" } if ($results.ErrorEvents -gt 10) { $results.Issues += "High number of error events ($($results.ErrorEvents)) may indicate persistent issues" } if ($results.FailedUpdates -gt $results.SuccessfulUpdates -and $results.UpdateAttempts -gt 0) { $results.Issues += "More failed updates ($($results.FailedUpdates)) than successful ones ($($results.SuccessfulUpdates))" } if ($results.LastSuccessfulUpdate -and $results.LastSuccessfulUpdate -lt (Get-Date).AddDays(-30)) { $daysSinceSuccess = [math]::Round(((Get-Date) - $results.LastSuccessfulUpdate).TotalDays) $results.Issues += "Last successful update was $daysSinceSuccess days ago" } # Check for specific problematic error codes $criticalErrorCodes = @('0x80240022', '0x8024402f', '0x80244007', '0x80070490', '0x800f0982') $foundCriticalErrors = $results.SignificantErrorCodes | Where-Object { $_.ErrorCode -in $criticalErrorCodes } if ($foundCriticalErrors) { $results.Issues += "Found critical error codes: $($foundCriticalErrors.ErrorCode -join ', ')" } } else { Write-WULog -Message "No relevant Windows Update events found in the specified time period" -LogPath $LogPath } # Summary Write-WULog -Message "Event log analysis summary:" -LogPath $LogPath Write-WULog -Message " Total relevant events: $($results.TotalEvents)" -LogPath $LogPath Write-WULog -Message " Critical errors: $($results.CriticalErrors)" -LogPath $LogPath Write-WULog -Message " Error events: $($results.ErrorEvents)" -LogPath $LogPath Write-WULog -Message " Warning events: $($results.WarningEvents)" -LogPath $LogPath Write-WULog -Message " Update attempts: $($results.UpdateAttempts)" -LogPath $LogPath Write-WULog -Message " Successful updates: $($results.SuccessfulUpdates)" -LogPath $LogPath Write-WULog -Message " Failed updates: $($results.FailedUpdates)" -LogPath $LogPath if ($results.Issues.Count -gt 0) { Write-WULog -Message "Issues identified:" -LogPath $LogPath foreach ($issue in $results.Issues) { Write-WULog -Message " - $issue" -Level Warning -LogPath $LogPath } } } catch { Write-WULog -Message "Critical error during event log analysis: $($_.Exception.Message)" -Level Error -LogPath $LogPath $results.Issues += "Critical error during event log analysis" } return $results } function Get-WUErrorCodeDescription { <# .SYNOPSIS Gets a human-readable description for Windows Update error codes. .PARAMETER ErrorCode The error code to describe (e.g., "0x80240022") #> param([string]$ErrorCode) $descriptions = @{ '0x80240022' = 'Windows Update Agent was stopped' '0x8024402f' = 'Windows Update service is not running' '0x80244007' = 'The update server did not return any updates' '0x80070490' = 'Component store corruption detected' '0x800f0982' = 'DISM failed - possible corruption or missing files' '0x80240034' = 'Update was not downloaded' '0x80240016' = 'Operation was cancelled' '0x80240020' = 'Operation was cancelled by user' '0x80240017' = 'Operation was cancelled due to disconnection' '0x80244019' = 'The size of the downloaded update is not what was expected' '0x8024401f' = 'There was a problem downloading the update' '0x80244018' = 'The downloaded update was not signed as expected' '0x8024401b' = 'The update handler requested a reboot' '0x80240031' = 'Network connection interrupted' '0x80240032' = 'Network connection interrupted while downloading' '0x8024400a' = 'Exceeded maximum number of retries for network requests' '0x8024400d' = 'There are more updates available' '0x80244022' = 'Windows Update Agent could not be updated because of an internal error' '0x8024402c' = 'Windows Update Agent could not be stopped' '0x80246007' = 'The search for updates was cancelled' '0x80246008' = 'An operation could not be completed because there is no logged-on interactive user' '0x8024400b' = 'The computer needs to be restarted before the search for updates can continue' '0x8024001e' = 'The operation could not be completed because the service or system was being shut down' '0x8024002e' = 'A callback was cancelled' '0x8024500c' = 'A required privilege is not held by the client' '0x80244009' = 'Operation failed because Windows Update Agent is updating itself' '0x8024a000' = 'Automatic Updates was unable to service incoming requests' '0x8024a002' = 'The task was cancelled before it was started' '0x8024a003' = 'The task is currently in progress' '0x8024a004' = 'The task was completed' '0x8024a005' = 'The task was stopped' '0x80072efe' = 'A connection with the server could not be established' '0x80072ee2' = 'The operation timed out' '0x80072f8f' = 'A security error occurred' '0x80248007' = 'An update could not be downloaded because the maximum file size was exceeded' '0xc1900200' = 'System Reserved Partition does not have enough free space' '0xc1900208' = 'Compatibility hold applied - upgrade blocked' '0x8007000d' = 'The data is invalid' '0x800706be' = 'The remote procedure call failed' '0x80070003' = 'The system cannot find the path specified' '0x80070005' = 'Access is denied' '0x8007000e' = 'Not enough storage is available to complete this operation' '0x80070057' = 'The parameter is incorrect' '0x800700c1' = 'This is not a valid Win32 application' '0x80070641' = 'Windows installer encountered an error' '0x80070643' = 'Fatal error during installation' '0x80070652' = 'Another installation is already in progress' '0x800706ba' = 'The RPC server is unavailable' '0x80072f76' = 'The specified server cannot perform the requested operation' } if ($descriptions.ContainsKey($ErrorCode)) { return $descriptions[$ErrorCode] } else { return "Unknown error code" } } |