diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DeviceInfoCollector.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DeviceInfoCollector.cs index eddaf3fe..05dfe17d 100644 --- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DeviceInfoCollector.cs +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DeviceInfoCollector.cs @@ -44,10 +44,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors { _logger.Info("EnrollmentTracker: collecting device info (at start)"); - CollectOSInfo(); - CollectBootTime(); - CollectNetworkAdapters(); - CollectDnsConfiguration(); + CollectOSInfoAndBootTime(); + CollectNetworkAndDnsConfiguration(); CollectProxyConfiguration(); var enrollmentType = CollectAutopilotProfile(); CollectSecureBootStatus(); @@ -71,66 +69,57 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors // Add more collectors here as needed } - private void CollectBootTime() + /// + /// Collects OS info and boot time in a single WMI query against Win32_OperatingSystem. + /// Previously these were three separate queries (Caption, LastBootUpTime via two searchers); + /// consolidated to a single round-trip. + /// Emits two events (os_info, boot_time) for backward compatibility. + /// + private void CollectOSInfoAndBootTime() { + string osName = string.Empty; + var bootData = new Dictionary(); + + // Single WMI query for both Caption (OS name) and LastBootUpTime try { - var data = new Dictionary(); - - // Query WMI for OS boot time - using (var searcher = new ManagementObjectSearcher("SELECT LastBootUpTime FROM Win32_OperatingSystem")) + using (var searcher = new ManagementObjectSearcher("SELECT Caption, LastBootUpTime FROM Win32_OperatingSystem")) { foreach (ManagementObject obj in searcher.Get()) { + osName = obj["Caption"]?.ToString() ?? string.Empty; + var lastBootUpTimeStr = obj["LastBootUpTime"]?.ToString(); if (!string.IsNullOrEmpty(lastBootUpTimeStr)) { - // WMI time format: yyyyMMddHHmmss.ffffff+/-UUU - // ManagementDateTimeConverter.ToDateTime returns DateTimeKind.Local, - // so we must explicitly convert to UTC and tag it as UTC to ensure - // correct ISO 8601 serialization with Z suffix. var bootTimeLocal = ManagementDateTimeConverter.ToDateTime(lastBootUpTimeStr); var bootTimeUtc = DateTime.SpecifyKind(bootTimeLocal.ToUniversalTime(), DateTimeKind.Utc); - data["bootTimeUtc"] = bootTimeUtc.ToString("o"); // always ends with Z - data["bootTime"] = bootTimeUtc.ToString("o"); // keep for compat, also UTC + bootData["bootTimeUtc"] = bootTimeUtc.ToString("o"); + bootData["bootTime"] = bootTimeUtc.ToString("o"); - // Calculate uptime using UTC to avoid local-clock drift var uptime = DateTime.UtcNow - bootTimeUtc; - data["uptimeMinutes"] = (int)uptime.TotalMinutes; - data["uptimeHours"] = uptime.TotalHours; + bootData["uptimeMinutes"] = (int)uptime.TotalMinutes; + bootData["uptimeHours"] = uptime.TotalHours; _logger.Debug($"Boot time (UTC): {bootTimeUtc:o}, Uptime: {uptime.TotalMinutes:F1} minutes"); - break; } + break; } } - - if (!data.ContainsKey("bootTime")) - { - data["note"] = "Boot time could not be determined"; - } - - EmitDeviceInfoEvent("boot_time", - data.ContainsKey("bootTime") - ? $"Last boot: {data["bootTime"]}" - : "Boot time unavailable", - data); } catch (Exception ex) { - _logger.Warning($"EnrollmentTracker: failed to collect boot time: {ex.Message}"); + _logger.Warning($"EnrollmentTracker: failed to query Win32_OperatingSystem: {ex.Message}"); } - } - private void CollectOSInfo() - { + // Emit os_info event try { var data = new Dictionary { { "version", Environment.OSVersion.Version.ToString() }, - { "osVersion", GetOsName() }, + { "osVersion", osName }, { "edition", (Registry.GetValue(RegKeyWindowsCurrentVersion, "EditionID", string.Empty) ?? string.Empty).ToString() }, { "compositionEdition", (Registry.GetValue(RegKeyWindowsCurrentVersion, "CompositionEditionID", string.Empty) ?? string.Empty).ToString() }, { "currentBuild", (Registry.GetValue(RegKeyWindowsCurrentVersion, "CurrentBuild", string.Empty) ?? string.Empty).ToString() }, @@ -168,42 +157,47 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors { _logger.Warning($"EnrollmentTracker: failed to collect OS info: {ex.Message}"); } - } - - private string GetOsName() - { - var osName = string.Empty; + // Emit boot_time event try { - using (var searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem")) + if (!bootData.ContainsKey("bootTime")) { - foreach (ManagementObject obj in searcher.Get()) - { - osName = obj["Caption"]?.ToString() ?? string.Empty; - break; - } + bootData["note"] = "Boot time could not be determined"; } - } - catch - { - // prevent OS info collection from failing the tracker - } - return osName; + EmitDeviceInfoEvent("boot_time", + bootData.ContainsKey("bootTime") + ? $"Last boot: {bootData["bootTime"]}" + : "Boot time unavailable", + bootData); + } + catch (Exception ex) + { + _logger.Warning($"EnrollmentTracker: failed to emit boot time event: {ex.Message}"); + } } - private void CollectNetworkAdapters() + /// + /// Collects network adapter and DNS configuration in a single WMI query. + /// Previously these were two separate queries against Win32_NetworkAdapterConfiguration; + /// consolidated to halve the WMI round-trips (each creates a new COM connection). + /// Emits two events (network_adapters, dns_configuration) for backward compatibility. + /// + private void CollectNetworkAndDnsConfiguration() { try { var adapters = new List>(); + var dnsServers = new List>(); + using (var searcher = new ManagementObjectSearcher( "SELECT Description, IPAddress, IPSubnet, DefaultIPGateway, MACAddress, DHCPEnabled, DHCPServer, DNSServerSearchOrder " + "FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")) { foreach (ManagementObject obj in searcher.Get()) { + // Network adapter info var adapter = new Dictionary { { "description", obj["Description"]?.ToString() }, @@ -225,32 +219,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors adapter["gateways"] = string.Join(", ", gateways); adapters.Add(adapter); - } - } - EmitDeviceInfoEvent("network_adapters", "Network adapters configuration", - new Dictionary - { - { "adapterCount", adapters.Count }, - { "adapters", adapters } - }); - } - catch (Exception ex) - { - _logger.Warning($"EnrollmentTracker: failed to collect network adapters: {ex.Message}"); - } - } - - private void CollectDnsConfiguration() - { - try - { - var dnsServers = new List>(); - using (var searcher = new ManagementObjectSearcher( - "SELECT Description, DNSServerSearchOrder FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")) - { - foreach (ManagementObject obj in searcher.Get()) - { + // DNS info (extracted from the same result set) var servers = obj["DNSServerSearchOrder"] as string[]; if (servers != null && servers.Length > 0) { @@ -263,6 +233,13 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors } } + EmitDeviceInfoEvent("network_adapters", "Network adapters configuration", + new Dictionary + { + { "adapterCount", adapters.Count }, + { "adapters", adapters } + }); + EmitDeviceInfoEvent("dns_configuration", "DNS server configuration", new Dictionary { @@ -271,7 +248,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors } catch (Exception ex) { - _logger.Warning($"EnrollmentTracker: failed to collect DNS config: {ex.Message}"); + _logger.Warning($"EnrollmentTracker: failed to collect network/DNS config: {ex.Message}"); } }