From 1bdb71be596fca20f6a321e0f6b69c36c7ab863b Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Mon, 17 Aug 2026 22:48:19 +0200 Subject: [PATCH] evtx export: resolve channel via LogFilePath, never by naming convention The %4-encodes-slash file naming is a convention, not a contract: the Microsoft-Autopilot-BootstrapperAgent/BootstrapperAgentServiceLogProvider channel writes plain BootstrapperAgentServiceLogProvider.evtx. Deriving the channel from the file name therefore produced an invalid wevtutil target (and the wrong advice to rename the tenant diag path cost a WDP test cycle). The channel is now resolved by scanning registered channels for a matching LogFilePath, with the %4 convention only as fallback. A failed export no longer skips the file - it falls through to the raw copy, which succeeds for inactive/archived evtx files. Co-Authored-By: Claude Fable 5 --- .../Runtime/DiagnosticsPackageService.cs | 63 +++++++++++++++---- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/DiagnosticsPackageService.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/DiagnosticsPackageService.cs index 38f3f133..45bbd8b4 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/DiagnosticsPackageService.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/DiagnosticsPackageService.cs @@ -534,14 +534,20 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime if (file.EndsWith(".evtx", StringComparison.OrdinalIgnoreCase)) { tempExport = TryExportEventLogChannel(file); - if (tempExport == null) + if (tempExport != null) { - tracker.RecordSkip(file, "evtx-export", length); - continue; + streamSource = tempExport; + try { length = new FileInfo(tempExport).Length; } + catch { /* keep original length estimate */ } + } + else + { + // Export failed (channel not resolvable / wevtutil error) — fall through + // to the raw copy. Inactive or archived evtx files are not locked, so + // the plain FileStream often still succeeds; if the channel IS active + // and locked, the existing per-file catch logs the failure. + _logger.Warning($"Event log export unavailable for {file} — attempting raw copy."); } - streamSource = tempExport; - try { length = new FileInfo(tempExport).Length; } - catch { /* keep original length estimate */ } } try @@ -596,19 +602,54 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime } } + /// + /// Resolves the event-log CHANNEL that writes to the given .evtx file by matching the + /// registered channels' LogFilePath — never by guessing a naming convention: most + /// channels follow the "%4 encodes '/'" file naming, but not all do (field case + /// 2026-08-17: Microsoft-Autopilot-BootstrapperAgent/BootstrapperAgentServiceLogProvider + /// writes plain "BootstrapperAgentServiceLogProvider.evtx"). Falls back to the %4 + /// convention only when no registered channel claims the file. + /// + internal static string ResolveChannelForEvtxFile(string evtxPath) + { + try + { + var target = Path.GetFullPath(evtxPath); + using (var session = new System.Diagnostics.Eventing.Reader.EventLogSession()) + { + foreach (var logName in session.GetLogNames()) + { + try + { + var config = new System.Diagnostics.Eventing.Reader.EventLogConfiguration(logName, session); + var logFile = config.LogFilePath; + if (string.IsNullOrEmpty(logFile)) continue; + var expanded = Path.GetFullPath(Environment.ExpandEnvironmentVariables(logFile)); + if (string.Equals(expanded, target, StringComparison.OrdinalIgnoreCase)) + return logName; + } + catch { /* individual channel unreadable — keep scanning */ } + } + } + } + catch { /* enumeration unavailable — fall back to convention */ } + + return Path.GetFileNameWithoutExtension(evtxPath).Replace("%4", "/"); + } + /// /// Exports the event-log channel behind a winevt .evtx file to a temp copy via - /// wevtutil epl. Channel name is derived from the file name (%4 decodes to '/', - /// e.g. "Microsoft-Autopilot-BootstrapperAgent%4BootstrapperAgentServiceLogProvider.evtx" - /// → "Microsoft-Autopilot-BootstrapperAgent/BootstrapperAgentServiceLogProvider"). - /// Returns the temp file path (caller deletes) or null when the export failed. + /// wevtutil epl (active channels hold their file exclusively locked, so a raw + /// copy fails). Channel resolution happens against the registered channels — see + /// . Returns the temp file path (caller deletes) + /// or null when the export failed. /// private string TryExportEventLogChannel(string evtxPath) { string tempPath = null; try { - var channel = Path.GetFileNameWithoutExtension(evtxPath).Replace("%4", "/"); + var channel = ResolveChannelForEvtxFile(evtxPath); tempPath = Path.Combine(Path.GetTempPath(), $"am-evtx-{Guid.NewGuid():N}.evtx"); var psi = new System.Diagnostics.ProcessStartInfo {