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