using System.Diagnostics.Eventing.Reader; using Microsoft.Extensions.Logging; using PatchProbe.Shared.Contracts; using PatchProbe.Shared.Models; namespace PatchProbe.Cli.Collectors; public sealed class EventCollector(ILogger logger) : ICollector> { private static readonly (string LogName, string? Provider)[] Sources = [ ("System", null), ("Microsoft-Windows-WindowsUpdateClient/Operational", "Microsoft-Windows-WindowsUpdateClient"), ("Microsoft-Windows-UpdateOrchestrator/Operational", "Microsoft-Windows-UpdateOrchestrator"), ]; private const int LookbackHours = 72; private const int MaxEventsPerSource = 50; public Task> CollectAsync(CancellationToken cancellationToken = default) { logger.LogInformation("Collecting recent Windows Update events (last {Hours}h)", LookbackHours); var events = new List(); var since = DateTime.UtcNow.AddHours(-LookbackHours); foreach (var (logName, provider) in Sources) { try { events.AddRange(ReadEvents(logName, provider, since)); } catch (Exception ex) { logger.LogDebug(ex, "Could not read event log: {LogName}", logName); } } logger.LogInformation("Collected {Count} update events", events.Count); return Task.FromResult(events); } private static IEnumerable ReadEvents(string logName, string? provider, DateTime since) { var providerFilter = provider != null ? $" and Provider[@Name='{provider}']" : string.Empty; var query = new EventLogQuery( logName, PathType.LogName, $"*[System[(Level<=3){providerFilter} and TimeCreated[@SystemTime>='{since:yyyy-MM-ddTHH:mm:ss.000Z}']]]"); using var reader = new EventLogReader(query); int count = 0; while (reader.ReadEvent() is EventRecord record && count < MaxEventsPerSource) { using (record) { yield return new UpdateEvent { EventId = record.Id, Source = record.ProviderName, LogName = record.LogName, Level = record.LevelDisplayName, TimeCreated = record.TimeCreated.HasValue ? new DateTimeOffset(record.TimeCreated.Value, TimeSpan.Zero) : null, Message = TryFormatMessage(record), }; count++; } } } private static string? TryFormatMessage(EventRecord record) { try { return record.FormatDescription(); } catch { return null; } } }