fixed OS info in sessions table

This commit is contained in:
Oliver Kieselbach
2026-03-07 09:50:38 +01:00
parent 8e8e8639f4
commit 323f46944e
13 changed files with 125 additions and 22 deletions
@@ -7,6 +7,7 @@ using System.Net.NetworkInformation;
using System.Text.Json;
using System.Threading.Tasks;
using AutopilotMonitor.Agent.Core.Logging;
using AutopilotMonitor.Agent.Core.Monitoring.Core;
using AutopilotMonitor.Shared.Models;
using Microsoft.Win32;
@@ -138,27 +139,27 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
// Emit os_info event
try
{
// Read individual registry values; reuse DeviceInfoProvider helpers where possible
var edition = (Registry.GetValue(RegKeyWindowsCurrentVersion, "EditionID", string.Empty) ?? string.Empty).ToString();
var compositionEdition = (Registry.GetValue(RegKeyWindowsCurrentVersion, "CompositionEditionID", string.Empty) ?? string.Empty).ToString();
var currentBuild = (Registry.GetValue(RegKeyWindowsCurrentVersion, "CurrentBuild", string.Empty) ?? string.Empty).ToString();
var buildBranch = (Registry.GetValue(RegKeyWindowsCurrentVersion, "BuildBranch", string.Empty) ?? string.Empty).ToString();
var displayVersion = DeviceInfoProvider.GetOsDisplayVersion();
var buildRevision = (Registry.GetValue(RegKeyWindowsCurrentVersion, "UBR", string.Empty) ?? string.Empty).ToString();
var data = new Dictionary<string, object>
{
{ "version", Environment.OSVersion.Version.ToString() },
{ "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() },
{ "buildBranch", (Registry.GetValue(RegKeyWindowsCurrentVersion, "BuildBranch", string.Empty) ?? string.Empty).ToString() },
{ "displayVersion", (Registry.GetValue(RegKeyWindowsCurrentVersion, "DisplayVersion", string.Empty) ?? string.Empty).ToString() },
{ "buildRevision", (Registry.GetValue(RegKeyWindowsCurrentVersion, "UBR", string.Empty) ?? string.Empty).ToString() }
{ "edition", edition },
{ "compositionEdition", compositionEdition },
{ "currentBuild", currentBuild },
{ "buildBranch", buildBranch },
{ "displayVersion", displayVersion },
{ "buildRevision", buildRevision }
};
object osVersionValue;
object displayVersionValue;
object currentBuildValue;
object buildRevisionValue;
var osVersion = data.TryGetValue("osVersion", out osVersionValue) ? osVersionValue?.ToString() ?? string.Empty : string.Empty;
var displayVersion = data.TryGetValue("displayVersion", out displayVersionValue) ? displayVersionValue?.ToString() ?? string.Empty : string.Empty;
var currentBuild = data.TryGetValue("currentBuild", out currentBuildValue) ? currentBuildValue?.ToString() ?? string.Empty : string.Empty;
var buildRevision = data.TryGetValue("buildRevision", out buildRevisionValue) ? buildRevisionValue?.ToString() ?? string.Empty : string.Empty;
var osVersion = osName;
var message = string.IsNullOrWhiteSpace(osVersion) ? "OS information collected" : osVersion;
if (!string.IsNullOrWhiteSpace(displayVersion))
@@ -82,5 +82,63 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
catch { }
return "Unknown";
}
/// <summary>
/// Returns the OS product name, e.g. "Microsoft Windows 11 Pro".
/// Uses WMI Win32_OperatingSystem.Caption which is the authoritative source.
/// </summary>
public static string GetOsName()
{
try
{
using (var searcher = new System.Management.ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem"))
{
foreach (var obj in searcher.Get())
{
return obj["Caption"]?.ToString() ?? string.Empty;
}
}
}
catch { }
return string.Empty;
}
/// <summary>
/// Returns the real OS build string, e.g. "26220.7934" (CurrentBuild.UBR).
/// Falls back to CurrentBuild alone if UBR is unavailable.
/// </summary>
public static string GetOsBuild()
{
try
{
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"))
{
var currentBuild = key?.GetValue("CurrentBuild")?.ToString();
if (string.IsNullOrEmpty(currentBuild))
return string.Empty;
var ubr = key?.GetValue("UBR");
return ubr != null ? $"{currentBuild}.{ubr}" : currentBuild;
}
}
catch { }
return string.Empty;
}
/// <summary>
/// Returns the OS display version, e.g. "25H2", "24H2".
/// </summary>
public static string GetOsDisplayVersion()
{
try
{
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"))
{
return key?.GetValue("DisplayVersion")?.ToString() ?? string.Empty;
}
}
catch { }
return string.Empty;
}
}
}
@@ -490,7 +490,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
Manufacturer = DeviceInfoProvider.GetManufacturer(),
Model = DeviceInfoProvider.GetModel(),
DeviceName = Environment.MachineName,
OsBuild = Environment.OSVersion.Version.ToString(),
OsName = DeviceInfoProvider.GetOsName(),
OsBuild = DeviceInfoProvider.GetOsBuild(),
OsDisplayVersion = DeviceInfoProvider.GetOsDisplayVersion(),
OsEdition = DeviceInfoProvider.GetOsEdition(),
OsLanguage = System.Globalization.CultureInfo.CurrentCulture.Name,
StartedAt = DateTime.UtcNow,
@@ -99,7 +99,9 @@ namespace AutopilotMonitor.Agent
Manufacturer = DeviceInfoProvider.GetManufacturer(),
Model = DeviceInfoProvider.GetModel(),
DeviceName = Environment.MachineName,
OsBuild = Environment.OSVersion.Version.ToString(),
OsName = DeviceInfoProvider.GetOsName(),
OsBuild = DeviceInfoProvider.GetOsBuild(),
OsDisplayVersion = DeviceInfoProvider.GetOsDisplayVersion(),
OsEdition = DeviceInfoProvider.GetOsEdition(),
OsLanguage = System.Globalization.CultureInfo.CurrentCulture.Name,
StartedAt = DateTime.UtcNow,
@@ -1 +1 @@
303
306
@@ -132,7 +132,9 @@ namespace AutopilotMonitor.Functions.Services
["Manufacturer"] = registration.Manufacturer ?? string.Empty,
["Model"] = registration.Model ?? string.Empty,
["DeviceName"] = registration.DeviceName ?? string.Empty,
["OsName"] = registration.OsName ?? string.Empty,
["OsBuild"] = registration.OsBuild ?? string.Empty,
["OsDisplayVersion"] = registration.OsDisplayVersion ?? string.Empty,
["OsEdition"] = registration.OsEdition ?? string.Empty,
["OsLanguage"] = registration.OsLanguage ?? string.Empty,
["IsUserDriven"] = registration.IsUserDriven,
@@ -1017,7 +1019,9 @@ namespace AutopilotMonitor.Functions.Services
IsPreProvisioned = entity.GetBoolean("IsPreProvisioned") ?? false,
IsHybridJoin = entity.GetBoolean("IsHybridJoin") ?? false,
ResumedAt = SafeGetDateTime(entity, "ResumedAt"),
OsName = entity.GetString("OsName") ?? string.Empty,
OsBuild = entity.GetString("OsBuild") ?? string.Empty,
OsDisplayVersion = entity.GetString("OsDisplayVersion") ?? string.Empty,
OsEdition = entity.GetString("OsEdition") ?? string.Empty,
OsLanguage = entity.GetString("OsLanguage") ?? string.Empty,
IsUserDriven = entity.GetBoolean("IsUserDriven") ?? false,
@@ -186,7 +186,9 @@ namespace AutopilotMonitor.Shared.Models
public bool IsHybridJoin { get; set; }
// Device detail fields — stored in the Sessions table but omitted from earlier versions
public string OsName { get; set; }
public string OsBuild { get; set; }
public string OsDisplayVersion { get; set; }
public string OsEdition { get; set; }
public string OsLanguage { get; set; }
public bool IsUserDriven { get; set; }
@@ -38,10 +38,20 @@ namespace AutopilotMonitor.Shared.Models
public string DeviceName { get; set; }
/// <summary>
/// OS build number
/// OS product name, e.g. "Microsoft Windows 11 Pro"
/// </summary>
public string OsName { get; set; }
/// <summary>
/// Real OS build number, e.g. "26220.7934" (CurrentBuild.UBR from registry)
/// </summary>
public string OsBuild { get; set; }
/// <summary>
/// OS display version, e.g. "25H2", "24H2"
/// </summary>
public string OsDisplayVersion { get; set; }
/// <summary>
/// OS edition (e.g., "Pro", "Enterprise")
/// </summary>
@@ -26,7 +26,9 @@ const ALL_COLUMNS: ColumnDef[] = [
{ key: "started", label: "Started", defaultVisible: true, sortKey: "startedAt" },
{ key: "country", label: "Country", defaultVisible: false, sortKey: "geoCountry", filterKey: "geoCountry" },
{ key: "agentVersion", label: "Agent Version", defaultVisible: false, sortKey: "agentVersion", filterKey: "agentVersion" },
{ key: "osName", label: "OS Name", defaultVisible: false, sortKey: "osName", filterKey: "osName" },
{ key: "osBuild", label: "OS Build", defaultVisible: false, sortKey: "osBuild", filterKey: "osBuild" },
{ key: "osDisplayVersion", label: "OS Version", defaultVisible: false, sortKey: "osDisplayVersion", filterKey: "osDisplayVersion" },
{ key: "osEdition", label: "OS Edition", defaultVisible: false, sortKey: "osEdition", filterKey: "osEdition" },
{ key: "osLanguage", label: "OS Language", defaultVisible: false, sortKey: "osLanguage", filterKey: "osLanguage" },
{ key: "actions", label: "Actions", defaultVisible: true, adminOnly: true },
@@ -337,7 +339,7 @@ export function SessionTable({
currentSort={sortColumn}
direction={sortDirection}
onSort={onSort}
className={["eventCount", "duration", "started", "country", "agentVersion", "osBuild", "osEdition", "osLanguage"].includes(col.key) ? "px-3" : undefined}
className={["eventCount", "duration", "started", "country", "agentVersion", "osName", "osBuild", "osDisplayVersion", "osEdition", "osLanguage"].includes(col.key) ? "px-3" : undefined}
filterKey={col.filterKey}
filterValues={col.filterKey ? getUniqueValues(col.filterKey) : undefined}
activeFilter={col.filterKey ? columnFilters[col.filterKey] : undefined}
@@ -569,6 +571,13 @@ function SessionCell({
);
}
case "osName":
return (
<td className="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
{session.osName || <span className="text-gray-300">—</span>}
</td>
);
case "osBuild":
return (
<td className="px-3 py-4 whitespace-nowrap text-sm text-gray-500 font-mono">
@@ -576,6 +585,13 @@ function SessionCell({
</td>
);
case "osDisplayVersion":
return (
<td className="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
{session.osDisplayVersion || <span className="text-gray-300">—</span>}
</td>
);
case "osEdition":
return (
<td className="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
@@ -510,7 +510,9 @@ export default function Home() {
session.geoRegion,
session.geoCity,
session.agentVersion,
session.osName,
session.osBuild,
session.osDisplayVersion,
session.osEdition,
session.osLanguage,
].join(" ").toLowerCase();
@@ -17,7 +17,9 @@ export interface Session {
geoCountry?: string;
geoRegion?: string;
geoCity?: string;
osName?: string;
osBuild?: string;
osDisplayVersion?: string;
osEdition?: string;
osLanguage?: string;
}
@@ -83,7 +83,9 @@ export interface SessionCsvData {
deviceName: string;
manufacturer: string;
model: string;
osName?: string;
osBuild?: string;
osDisplayVersion?: string;
osEdition?: string;
osLanguage?: string;
isUserDriven?: boolean;
@@ -105,7 +107,7 @@ export function generateSessionCsvExport(session: SessionCsvData): string {
const esc = (v: string | undefined | null) => `"${String(v ?? "").replace(/"/g, '""')}"`;
// Columns match the Sessions Azure Table Storage schema exactly
// PartitionKey = TenantId, RowKey = SessionId
const header = "PartitionKey,RowKey,SerialNumber,DeviceName,Manufacturer,Model,OsBuild,OsEdition,OsLanguage,IsUserDriven,IsPreProvisioned,StartedAt,CompletedAt,AgentVersion,EnrollmentType,CurrentPhase,Status,EventCount,FailureReason,LastEventAt,DurationSeconds,DiagnosticsBlobName";
const header = "PartitionKey,RowKey,SerialNumber,DeviceName,Manufacturer,Model,OsName,OsBuild,OsDisplayVersion,OsEdition,OsLanguage,IsUserDriven,IsPreProvisioned,StartedAt,CompletedAt,AgentVersion,EnrollmentType,CurrentPhase,Status,EventCount,FailureReason,LastEventAt,DurationSeconds,DiagnosticsBlobName";
const row = [
esc(session.tenantId),
esc(session.sessionId),
@@ -113,7 +115,9 @@ export function generateSessionCsvExport(session: SessionCsvData): string {
esc(session.deviceName),
esc(session.manufacturer),
esc(session.model),
esc(session.osName),
esc(session.osBuild),
esc(session.osDisplayVersion),
esc(session.osEdition),
esc(session.osLanguage),
String(session.isUserDriven ?? ""),
File diff suppressed because one or more lines are too long