dist/temp/WindowsUpdateTools/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
            },
            @{
                LogName = 'System'
                Description = 'System Events'
                Critical = $true
            },
            @{
                LogName = 'Application'
                Description = 'Application Events'
                Critical = $false
            }
        )

        $allRelevantEvents = @()

        foreach ($logSource in $logSources) {
            $sourceResult = [PSCustomObject]@{
                LogName = $logSource.LogName
                Description = $logSource.Description
                EventsFound = 0
                ErrorEvents = 0
                CriticalEvents = 0
                WarningEvents = 0
                Available = $false
                ErrorMessage = $null
            }

            try {
                # 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
                $relevantEvents = $events | Where-Object {
                    $_.Message -like "*update*" -or 
                    $_.Message -like "*0x8*" -or 
                    $_.ProviderName -like "*Update*" -or
                    $_.ProviderName -like "*BITS*" -or
                    $_.ProviderName -like "*Servicing*" -or
                    $_.Id -in @(20, 24, 25, 31, 34, 35, 19, 43, 44, 1014, 1015, 1016, 1017, 1018, 1020)  # Common Windows Update event IDs
                }

                $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

            }
            catch {
                if ($_.Exception.Message -like '*No events were found*') {
                    $sourceResult.Available = $true
                } else {
                    Write-WULog -Message " Error accessing $($logSource.LogName): $($_.Exception.Message)" -Level Warning -LogPath $LogPath
                    $sourceResult.ErrorMessage = $_.Exception.Message
                    $sourceResult.Available = $false
                }
            }

            $results.EventSources += $sourceResult
        }

        # Log a single summary of all sources
        $totalFound = ($results.EventSources | Measure-Object -Property EventsFound -Sum).Sum
        $sourceSummary = $results.EventSources | Where-Object { $_.EventsFound -gt 0 } | ForEach-Object { "$($_.LogName -replace 'Microsoft-Windows-WindowsUpdateClient/', 'WUClient-'): $($_.EventsFound)" }
        if ($sourceSummary.Count -gt 0) {
            Write-WULog -Message "Found $totalFound events across logs: $($sourceSummary -join ', ')" -LogPath $LogPath
        } else {
            Write-WULog -Message "No relevant events found in any logs" -LogPath $LogPath
        }

        $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
                    }
                }

                # Store error codes for summary (no detailed logging here to avoid redundancy)
            }

            # 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: $($results.CriticalErrors), Errors: $($results.ErrorEvents), Warnings: $($results.WarningEvents)" -LogPath $LogPath
        Write-WULog -Message " Update attempts: $($results.UpdateAttempts), Successful: $($results.SuccessfulUpdates), Failed: $($results.FailedUpdates)" -LogPath $LogPath

        if ($results.SignificantErrorCodes.Count -gt 0) {
            Write-WULog -Message " Top error codes:" -LogPath $LogPath
            foreach ($errorInfo in $results.SignificantErrorCodes[0..2]) {  # Reduced from 5 to 3 to be more concise
                Write-WULog -Message " $($errorInfo.ErrorCode) ($($errorInfo.Occurrences)x): $($errorInfo.Description)" -LogPath $LogPath
            }
        }

        if ($results.LastSuccessfulUpdate) {
            $daysSinceSuccess = [math]::Round(((Get-Date) - $results.LastSuccessfulUpdate).TotalDays, 1)
            Write-WULog -Message " Last successful update: $($results.LastSuccessfulUpdate.ToString('yyyy-MM-dd HH:mm')) ($daysSinceSuccess days ago)" -LogPath $LogPath
        } else {
            Write-WULog -Message " Last successful update: Not found in analysis period" -LogPath $LogPath
        }

        if ($results.LastFailedUpdate) {
            $daysSinceFailure = [math]::Round(((Get-Date) - $results.LastFailedUpdate).TotalDays, 1)
            Write-WULog -Message " Last failed update: $($results.LastFailedUpdate.ToString('yyyy-MM-dd HH:mm')) ($daysSinceFailure days ago)" -LogPath $LogPath
        }

        if ($results.CommonFailures.Count -gt 0) {
            Write-WULog -Message " Common failure patterns:" -LogPath $LogPath
            foreach ($pattern in $results.CommonFailures[0..1]) {  # Reduced from 3 to 2 patterns
                $truncatedPattern = if ($pattern.Pattern.Length -gt 60) { $pattern.Pattern.Substring(0, 60) + "..." } else { $pattern.Pattern }
                Write-WULog -Message " $($pattern.Occurrences)x: $truncatedPattern" -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 = @{
        # Windows Update Agent / WUAPI
        '0x80240022' = 'WU_E_ALL_UPDATES_FAILED - Operation failed for all updates'                               
        '0x8024402f' = 'WU_E_PT_ECP_SUCCEEDED_WITH_ERRORS - External cab processing finished with errors'        
        '0x80244007' = 'WU_E_PT_SOAPCLIENT_SOAPFAULT - SOAP fault returned by server'                            
        '0x80240034' = 'WU_E_DOWNLOAD_FAILED - Update failed to download'                                        
        '0x80240016' = 'WU_E_INSTALL_NOT_ALLOWED - Another install/reboot in progress'                           
        '0x80240020' = 'WU_E_NO_INTERACTIVE_USER - No logged-on interactive user'                                
        '0x80240017' = 'WU_E_NOT_APPLICABLE - Update not applicable to this system'                              
        '0x80244019' = 'WU_E_PT_HTTP_STATUS_NOT_FOUND - HTTP 404 from update server'                             
        '0x8024401f' = 'WU_E_PT_HTTP_STATUS_SERVER_ERROR - HTTP 500 from update server'                          
        '0x80244018' = 'WU_E_PT_HTTP_STATUS_FORBIDDEN - HTTP 403 from update server'                             
        '0x8024401b' = 'WU_E_PT_HTTP_STATUS_PROXY_AUTH_REQ - HTTP 407 proxy auth required'                       
        '0x80240031' = 'WU_E_INVALID_FILE - File is in the wrong format'                                         
        '0x80240032' = 'WU_E_INVALID_CRITERIA - Search criteria string is invalid'                               
        '0x8024400a' = 'WU_E_PT_SOAPCLIENT_PARSE - SOAP response was not well-formed'                            
        '0x8024400d' = 'WU_E_PT_SOAP_CLIENT - SOAP client found message malformed'                               
        '0x80244022' = 'WU_E_PT_HTTP_STATUS_SERVICE_UNAVAIL - HTTP 503 service unavailable'                      
        '0x8024402c' = 'WU_E_PT_WINHTTP_NAME_NOT_RESOLVED - Name could not be resolved'                          
        '0x80246007' = 'WU_E_DM_NOTDOWNLOADED - Update hasnt been downloaded'                                   
        '0x80246008' = 'WU_E_DM_FAILTOCONNECTTOBITS - Download manager couldnt connect to BITS'                 
        '0x8024400b' = 'WU_E_PT_SOAP_VERSION - SOAP version mismatch'                                            
        '0x8024001e' = 'WU_E_SERVICE_STOP - Operation aborted: service or system shutting down'                  
        '0x8024002e' = 'WU_E_WU_DISABLED - Access to unmanaged server is disallowed by policy'                   
        '0x8024500c' = 'WU_E_REDIRECTOR_DISABLED - Install prevented by “Do not connect to any WU location”'     
        '0x80244009' = 'WU_E_PT_SOAPCLIENT_READ - SOAP client failed while reading response'  
        '0x80240438' = 'WU_E_PT_ENDPOINT_UNREACHABLE - No route or network connectivity to the Windows Update endpoint'           
        '0x80246010' = 'WU_E_DM_DOWNLOADSANDBOXNOTFOUND - The sandbox directory for the downloaded update was not found'
        '0x80242016' = 'WU_E_UH_POSTREBOOTUNEXPECTEDSTATE - The state of the update after its post-reboot operation was unexpected'
        '0x8024402d' = 'WU_E_PT_LOAD_SHEDDING - The server is shedding load (transient; retry later)'               

        # Automatic Updates (AU) component
        '0x8024a000' = 'WU_E_AU_NOSERVICE - Automatic Updates cant service requests'
        '0x8024a002' = 'WU_E_AU_NONLEGACYSERVER - Legacy AU client stopped; WSUS upgraded'
        '0x8024a003' = 'WU_E_AU_LEGACYCLIENTDISABLED - Legacy AU client disabled'
        '0x8024a004' = 'WU_E_AU_PAUSED - Automatic Updates is paused'                                            
        '0x8024a005' = 'WU_E_AU_NO_REGISTERED_SERVICE - No unmanaged service registered with AU'                 

        # Data-store & expression engine
        '0x80248007' = 'WU_E_DS_NODATA - Requested information isnt in the data store' 
        
        # .NET exceptions (often surfaced by WU)
        '0xe0434352' = 'Unhandled .NET (CLR) exception - generic “STATUS_CLR_EXCEPTION” wrapper'

        # WinHTTP / WinINET connectivity
        '0x80072efe' = 'ERROR_WINHTTP_CONNECTION_ERROR - A connection with the server couldnt be established'
        '0x80072ee2' = 'ERROR_WINHTTP_TIMEOUT - The operation timed out'
        '0x80072f8f' = 'ERROR_WINHTTP_SECURE_FAILURE - A security/SSL error occurred'
        '0x80072ee7' = 'ERROR_WINHTTP_NAME_NOT_RESOLVED - The server name or address couldnt be resolved'
        '0x80072f76' = 'ERROR_WINHTTP_INVALID_SERVER_RESPONSE - The specified server cant perform the requested operation'
        '0x80072af9' = 'WSAHOST_NOT_FOUND - Host not found (DNS couldnt resolve the server name)'

        # Setup / Feature update
        '0xc1900200' = 'MOSETUP_E_COMPAT_SCANONLY - PC doesnt meet minimum requirements'
        '0xc1900208' = 'MOSETUP_E_COMPAT_INSTALLREQ_BLOCK - Upgrade blocked by incompatible app/driver'

        # DISM / Servicing
        '0x80070490' = 'ERROR_NOT_FOUND - Component or element not found (often indicates component-store corruption)'
        '0x800f0982' = 'PSFX_E_MATCHING_COMPONENT_NOT_FOUND - Missing files/component store corruption'

        # Generic errors
        '0x8007000d' = 'ERROR_INVALID_DATA - The data is invalid'
        '0x800706be' = 'RPC_S_CALL_FAILED - The remote procedure call failed'
        '0x80070003' = 'ERROR_PATH_NOT_FOUND - The system cant find the path specified'
        '0x80070005' = 'ERROR_ACCESS_DENIED - Access is denied'
        '0x8007000e' = 'ERROR_OUTOFMEMORY - Not enough storage is available to complete the operation'
        '0x80070057' = 'ERROR_INVALID_PARAMETER - The parameter is incorrect'
        '0x800700c1' = 'ERROR_BAD_EXE_FORMAT - Not a valid Win32 application'
        '0x80070641' = 'ERROR_INSTALL_SERVICE_FAILURE - Windows Installer service couldnt be accessed'
        '0x80070643' = 'ERROR_INSTALL_FAILURE - Fatal error during installation'
        '0x80070652' = 'ERROR_INSTALL_ALREADY_RUNNING - Another installation is already in progress'
        '0x800706ba' = 'RPC_S_SERVER_UNAVAILABLE - The RPC server is unavailable'
        '0x8007045b' = 'ERROR_SHUTDOWN_IN_PROGRESS - Windows is shutting down; setup/updates stop until the next boot' 
        '0x8900002F' = 'Volumes cant be optimized - file-system type not supported'
        '0x0014dba2' = 'Fault offset - not an error code; ignore'
        '0x800721ce' = 'ERROR_DS_DRA_DB_ERROR - Directory-service call hit a database error' 
        '0x80070002' = 'ERROR_FILE_NOT_FOUND - The system cannot find the file specified (usually missing update payload)'
        '0x80070006' = 'ERROR_INVALID_HANDLE - The handle supplied to the API call is invalid'      
        '0x800706e1' = 'RPC_S_ENTRY_NOT_FOUND - The entry is not found'
        '0xc19001e1' = 'MOSETUP_E_PROCESS_SUSPENDED - The installation process was suspended'
        '0x80004005' = 'E_FAIL - Unspecified error generic catch-all'
        '0x1dbe069a' = 'Fault offset - ignore for update-error look-ups'
        '0xc0000005' = 'STATUS_ACCESS_VIOLATION - A process tried to read or write memory at an invalid address'
        
        # App-package / MSIX
        '0x80073d02' = 'ERROR_PACKAGE_IN_USE - Package install failed; resources currently in use'

        # VSS
        '0x80042318' = 'VSS_E_WRITER_INFRASTRUCTURE - Error detected in VSS while contacting writers'            

        # Windows Search (documented only in event logs)
        '0x80940db9' = 'SEARCH_E_INDEX_CORRUPT - Outlook/Windows Search index corruption'                        
        '0x80940d23' = 'SEARCH_E_CORRUPT_PERSISTED_FILE - Search cant index Outlook data file'
        '0x80040d07' = 'The specified address was excluded from the index'  
        '0x80040d7b' = 'GTHR_E_TIMEOUT - The repository did not respond within the specified timeout period'           
    }
    
    if ($descriptions.ContainsKey($ErrorCode)) {
        return $descriptions[$ErrorCode]
    } else {
        return "Unknown error code"
    }
}