log ignored agent collector keys for later UI enhancements
The endpoint agent now sends disk, bios, memory, processor, osinfo, and domain telemetry. The server does not persist these yet, so ingest logs any unpersisted payload keys with a compact preview. This makes incoming telemetry observable in the logs before any UI or schema work, so we can confirm the agent is sending it and enhance the UI later.
This commit is contained in:
committed by
cproudlock
parent
2a4b1bb040
commit
57845e1a9a
@@ -29,6 +29,16 @@ public sealed class EndpointReport
|
||||
[JsonPropertyName("interfaces")] public object[]? Interfaces { get; set; }
|
||||
[JsonPropertyName("scheduledtasks")] public object[]? ScheduledTasks { get; set; }
|
||||
[JsonPropertyName("events")] public EndpointEventRecord[]? Events { get; set; }
|
||||
// Additive inventory telemetry. The server does not persist these yet; its
|
||||
// ingest logs them as unknown keys (plugins/endpoints/plugin.py) so the
|
||||
// incoming values are observable before any DB column or UI card exists.
|
||||
// Keys stay lowercase/no-underscore to match AGENT-CONTRACT.md.
|
||||
[JsonPropertyName("disks")] public object[]? Disks { get; set; }
|
||||
[JsonPropertyName("bios")] public object? Bios { get; set; }
|
||||
[JsonPropertyName("memory")] public object? Memory { get; set; }
|
||||
[JsonPropertyName("processor")] public object? Processor { get; set; }
|
||||
[JsonPropertyName("osinfo")] public object? OsInfo { get; set; }
|
||||
[JsonPropertyName("domain")] public string? Domain { get; set; }
|
||||
}
|
||||
|
||||
public sealed class EndpointEventRecord
|
||||
|
||||
@@ -50,6 +50,12 @@ public sealed class EndpointCollector : ICollector
|
||||
Services = GetMonitoredTargets(),
|
||||
Interfaces = GetInterfaces(),
|
||||
Events = GetCuratedEvents(),
|
||||
Disks = GetDisks(),
|
||||
Bios = GetBios(),
|
||||
Memory = GetMemory(),
|
||||
Processor = GetProcessor(),
|
||||
OsInfo = GetOsInfo(),
|
||||
Domain = GetDomain(),
|
||||
};
|
||||
|
||||
if (report.LastBootTime is DateTimeOffset boot)
|
||||
@@ -460,6 +466,194 @@ public sealed class EndpointCollector : ICollector
|
||||
return records.ToArray();
|
||||
}
|
||||
|
||||
// Fixed local disks with capacity/free so the server can flag a PC that is
|
||||
// running out of space. Win32_LogicalDisk DriveType 3 = local fixed disk;
|
||||
// network/removable drives are skipped. Bytes stay as longs; the server can
|
||||
// derive percentages. Defensive: a locked WMI hive yields an empty list.
|
||||
private object[] GetDisks()
|
||||
{
|
||||
var result = new List<object>();
|
||||
try
|
||||
{
|
||||
using var searcher = new ManagementObjectSearcher(
|
||||
"SELECT DeviceID, VolumeName, FileSystem, Size, FreeSpace " +
|
||||
"FROM Win32_LogicalDisk WHERE DriveType = 3");
|
||||
foreach (var mo in searcher.Get())
|
||||
{
|
||||
var size = ToLong(mo["Size"]);
|
||||
var free = ToLong(mo["FreeSpace"]);
|
||||
result.Add(new
|
||||
{
|
||||
drive = mo["DeviceID"]?.ToString()?.Trim() ?? "",
|
||||
label = mo["VolumeName"]?.ToString()?.Trim() ?? "",
|
||||
filesystem = mo["FileSystem"]?.ToString()?.Trim() ?? "",
|
||||
sizebytes = size,
|
||||
freebytes = free,
|
||||
usedbytes = size is long total && free is long available
|
||||
? total - available : (long?)null,
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
logger.LogDebug(error, "Disk enumeration blocked");
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
// Firmware identity from Win32_BIOS: vendor, version string, and release
|
||||
// date. Lets the server track BIOS/UEFI levels for a fleet update campaign.
|
||||
private object? GetBios()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var searcher = new ManagementObjectSearcher(
|
||||
"SELECT Manufacturer, SMBIOSBIOSVersion, ReleaseDate FROM Win32_BIOS");
|
||||
foreach (var mo in searcher.Get())
|
||||
{
|
||||
return new
|
||||
{
|
||||
vendor = mo["Manufacturer"]?.ToString()?.Trim() ?? "",
|
||||
version = mo["SMBIOSBIOSVersion"]?.ToString()?.Trim() ?? "",
|
||||
releasedate = ToBiosDate(mo["ReleaseDate"]?.ToString()),
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
logger.LogDebug(error, "BIOS query failed");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Physical memory totals. TotalVisibleMemorySize/FreePhysicalMemory come
|
||||
// from Win32_OperatingSystem in kilobytes; normalize to bytes so the wire
|
||||
// stays in one unit with the disk figures.
|
||||
private object? GetMemory()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var searcher = new ManagementObjectSearcher(
|
||||
"SELECT TotalVisibleMemorySize, FreePhysicalMemory " +
|
||||
"FROM Win32_OperatingSystem");
|
||||
foreach (var mo in searcher.Get())
|
||||
{
|
||||
var totalKb = ToLong(mo["TotalVisibleMemorySize"]);
|
||||
var freeKb = ToLong(mo["FreePhysicalMemory"]);
|
||||
return new
|
||||
{
|
||||
totalbytes = totalKb is long total ? total * 1024 : (long?)null,
|
||||
freebytes = freeKb is long free ? free * 1024 : (long?)null,
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
logger.LogDebug(error, "Memory query failed");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// CPU identity and core counts from Win32_Processor (first socket).
|
||||
private object? GetProcessor()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var searcher = new ManagementObjectSearcher(
|
||||
"SELECT Name, NumberOfCores, NumberOfLogicalProcessors, " +
|
||||
"MaxClockSpeed FROM Win32_Processor");
|
||||
foreach (var mo in searcher.Get())
|
||||
{
|
||||
return new
|
||||
{
|
||||
name = mo["Name"]?.ToString()?.Trim() ?? "",
|
||||
cores = ToLong(mo["NumberOfCores"]),
|
||||
logicalprocessors = ToLong(mo["NumberOfLogicalProcessors"]),
|
||||
maxclockmhz = ToLong(mo["MaxClockSpeed"]),
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
logger.LogDebug(error, "Processor query failed");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// OS build/version detail beyond the caption already sent as osname. Lets
|
||||
// the server track patch level (build number) across the fleet.
|
||||
private object? GetOsInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var searcher = new ManagementObjectSearcher(
|
||||
"SELECT Version, BuildNumber, OSArchitecture, InstallDate " +
|
||||
"FROM Win32_OperatingSystem");
|
||||
foreach (var mo in searcher.Get())
|
||||
{
|
||||
return new
|
||||
{
|
||||
version = mo["Version"]?.ToString()?.Trim() ?? "",
|
||||
build = mo["BuildNumber"]?.ToString()?.Trim() ?? "",
|
||||
architecture = mo["OSArchitecture"]?.ToString()?.Trim() ?? "",
|
||||
installdate = ToBiosDate(mo["InstallDate"]?.ToString()),
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
logger.LogDebug(error, "OS info query failed");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// AD domain or workgroup this PC is joined to. PartOfDomain distinguishes a
|
||||
// domain-joined box from a workgroup one.
|
||||
private string? GetDomain()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var searcher = new ManagementObjectSearcher(
|
||||
"SELECT Domain, PartOfDomain FROM Win32_ComputerSystem");
|
||||
foreach (var mo in searcher.Get())
|
||||
{
|
||||
var domain = mo["Domain"]?.ToString()?.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(domain))
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
logger.LogDebug(error, "Domain query failed");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static long? ToLong(object? value)
|
||||
{
|
||||
if (value is null)
|
||||
return null;
|
||||
try { return Convert.ToInt64(value); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
// Win32 CIM datetimes (yyyymmddHHMMSS.ffffff+zzz) render as an ISO-8601 UTC
|
||||
// string; anything unparseable passes through untouched so nothing is lost.
|
||||
private string? ToBiosDate(string? raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
return null;
|
||||
try
|
||||
{
|
||||
return ManagementDateTimeConverter.ToDateTime(raw)
|
||||
.ToUniversalTime().ToString("o");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return raw.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
private string? QueryFirst(string wql, string property)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user