using System; using System.Collections.Generic; using System.Linq; using System.Management; using System.Net; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.DirectoryServices.AccountManagement; using System.Security.Principal; using Microsoft.Win32; // Added for Registry access using System.Text; using System.Text.Json; namespace Inventory.Core { public class SystemInfoCollector { public ConsumerType Consumer { get; set; } = ConsumerType.Unknown; public string GetComputerName() { return Environment.MachineName; } public string GetDeviceType() { if (!OperatingSystem.IsWindows()) return "N/A"; // Using Win32_SystemEnclosure to determine device type try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_SystemEnclosure"); foreach (ManagementObject mo in searcher.Get()) { foreach (int chassisType in (ushort[])mo["ChassisTypes"]) { switch (chassisType) { case 3: // Desktop case 4: // Low Profile Desktop case 5: // Pizza Box case 6: // Mini Tower case 7: // Tower return "Desktop"; case 8: // Portable case 9: // Laptop case 10: // Notebook case 11: // Hand Held case 12: // Docking Station case 14: // Sub Notebook return "Laptop"; case 23: // Rack Mount Chassis return "Server"; } } } } catch { /* Ignore WMI errors */ } // Fallback to checking for a battery try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Battery"); if (searcher.Get().Count > 0) { return "Laptop"; } } catch { /* Ignore WMI errors */ } return "Unknown"; } public string? GetSystemSerialNumber() { if (!OperatingSystem.IsWindows()) return "N/A"; // 1. Primary Method: Win32_ComputerSystemProduct is often the most reliable. string? serial = GetWmiProperty("Win32_ComputerSystemProduct", "IdentifyingNumber"); if (IsValidSerialNumber(serial)) { return serial; } // 2. Fallback Method: Win32_BIOS is a common alternative. string? biosSerial = GetWmiProperty("Win32_BIOS", "SerialNumber"); if (IsValidSerialNumber(biosSerial)) { return biosSerial; } // 3. If both are invalid, prefer the first non-empty one found, otherwise return "N/A". return !string.IsNullOrWhiteSpace(serial) ? serial : biosSerial ?? "N/A"; } public string? GetMotherboardSerialNumber() { if (!OperatingSystem.IsWindows()) return "N/A"; string? serial = GetWmiProperty("Win32_BaseBoard", "SerialNumber"); return IsValidSerialNumber(serial) ? serial : "N/A"; } public string? GetSystemUUID() { if (!OperatingSystem.IsWindows()) return "N/A"; try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT UUID FROM Win32_ComputerSystemProduct"); ManagementObject? mo = searcher.Get().OfType().FirstOrDefault(); return mo?["UUID"]?.ToString()?.Trim(); } catch { return "N/A"; } } public string? GetProcessor() { if (!OperatingSystem.IsWindows()) return "N/A"; try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_Processor"); ManagementObject? mo = searcher.Get().OfType().FirstOrDefault(); return mo?["Name"]?.ToString()?.Trim(); } catch { return "N/A"; } } public string GetTotalRAM() { if (!OperatingSystem.IsWindows()) return "N/A"; try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem"); ManagementObject? mo = searcher.Get().OfType().FirstOrDefault(); if (mo != null) { ulong ramBytes = (ulong)mo["TotalPhysicalMemory"]; double ramGB = Math.Round(ramBytes / (1024.0 * 1024.0 * 1024.0)); return $"{ramGB} GB"; } return "N/A"; } catch { return "N/A"; } } public List GetGPUs() { var gpus = new List(); if (!OperatingSystem.IsWindows()) return gpus; try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_VideoController"); foreach (ManagementObject mo in searcher.Get()) { gpus.Add(mo["Name"]?.ToString()?.Trim() ?? ""); } } catch { /* Ignore WMI errors */ } return gpus; } public List GetStorage() { var drives = new List(); if (!OperatingSystem.IsWindows()) return drives; try { ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\Microsoft\Windows\Storage", "SELECT * FROM MSFT_PhysicalDisk"); foreach (ManagementObject mo in searcher.Get()) { string mediaType = "Unknown"; switch ((ushort)mo["MediaType"]) { case 3: mediaType = "HDD"; break; case 4: mediaType = "SSD"; break; } drives.Add(new StorageDeviceInfo { Model = mo["Model"]?.ToString()?.Trim(), SerialNumber = mo["SerialNumber"]?.ToString()?.Trim(), MediaType = mediaType, InterfaceType = mo["BusType"]?.ToString()?.Trim(), Size = (ulong)mo["Size"] }); } } catch { /* Ignore WMI errors */ } return drives; } public List GetPrinters() { var printers = new List(); if (!OperatingSystem.IsWindows()) return printers; var ignoredPrinterNames = new List { "Microsoft Print to PDF", "Microsoft XPS Document Writer", "OneNote", "Fax", "Send to OneNote", "Adobe PDF", "CutePDF", "Bullzip", "doPDF", "Foxit", "PrimoPDF", "PDF Architect", "Snagit", "WebEx", "AnyDesk" }; try { // PRIMARY METHOD: Use Win32_Printer. This is the most detailed and works when run with user context (e.g., AdminTool). ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Printer"); foreach (ManagementObject mo in searcher.Get()) { string? name = mo["Name"]?.ToString()?.Trim(); if (string.IsNullOrEmpty(name) || ignoredPrinterNames.Any(p => name.Contains(p, StringComparison.OrdinalIgnoreCase))) { continue; } // Attempt to get serial number from PNPDeviceID (mostly for USB) string? pnpDeviceId = mo["PNPDeviceID"]?.ToString(); string? serialNumber = GetSerialFromPnpId(pnpDeviceId); string? ipAddress = null; bool isNetwork = (bool)mo["Network"]; string? portName = mo["PortName"]?.ToString()?.Trim(); string? serverName = mo["ServerName"]?.ToString()?.Trim(); // Key for shared printers // If it's a shared printer from another server, resolve the server's IP. if (!string.IsNullOrEmpty(serverName)) { ipAddress = ResolveHostnameToIp(serverName); } // Otherwise, if it's a local network printer, get IP from the port name. else if (isNetwork || (portName?.StartsWith("IP_") ?? false)) { ipAddress = GetIpFromPortName(portName); } if (string.IsNullOrEmpty(serialNumber) && !string.IsNullOrEmpty(ipAddress)) { if (!string.IsNullOrEmpty(ipAddress)) { serialNumber = GetPrinterSerialViaSnmp(ipAddress); } } if (!string.IsNullOrEmpty(pnpDeviceId) && pnpDeviceId.Contains("USB")) { serialNumber ??= GetSerialFromPnpId(pnpDeviceId); } printers.Add(new PrinterInfo { Name = name, DriverName = mo["DriverName"]?.ToString()?.Trim(), IsShared = (bool)mo["Shared"], IsNetwork = isNetwork, HostName = mo["SystemName"]?.ToString()?.Trim(), PortName = portName, SerialNumber = serialNumber }); } } catch { /* Ignore WMI errors, proceed to fallback */ } // FALLBACK METHOD: If Win32_Printer found nothing (common for LocalSystem service), query the registry. if (printers.Count == 0) { try { using (RegistryKey? printKey = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Print\Printers")) { if (printKey != null) { foreach (string printerName in printKey.GetSubKeyNames()) { if (ignoredPrinterNames.Any(p => printerName.Contains(p, StringComparison.OrdinalIgnoreCase))) { continue; } using (RegistryKey? printerSubKey = printKey.OpenSubKey(printerName)) { if (printerSubKey == null) continue; string? portName = printerSubKey.GetValue("Port")?.ToString(); bool isNetwork = portName?.StartsWith("IP_") ?? false; string? serialNumber = null; // For network printers, try to get the IP from the port name and query SNMP if (isNetwork) { string? ipAddress = GetIpFromPortName(portName); if (!string.IsNullOrEmpty(ipAddress)) { serialNumber = GetPrinterSerialViaSnmp(ipAddress); } } var printerInfo = new PrinterInfo { Name = printerSubKey.GetValue("Name")?.ToString() ?? printerName, DriverName = printerSubKey.GetValue("Printer Driver")?.ToString(), PortName = portName, IsShared = (printerSubKey.GetValue("Attributes") is int attributes && (attributes & 0x8) != 0), // PRINTER_ATTRIBUTE_SHARED IsNetwork = isNetwork, SerialNumber = serialNumber }; printers.Add(printerInfo); } } } } } catch { /* Silently ignore registry errors */ } } return printers.DistinctBy(p => p.Name).ToList(); } private string? GetSerialFromPnpId(string? pnpDeviceId) { if (string.IsNullOrEmpty(pnpDeviceId) || !pnpDeviceId.Contains("USB")) { return null; } var parts = pnpDeviceId.Split('\\'); // The serial is usually the last part of the ID for USB devices. // Example: USBPRINT\HEWLETT-PACKARDHP_LASERJET_PROFESSIONAL_M1212NF_MFP\6&1D4A4F6D&0&USB001 // Or: USB\VID_03F0&PID_3B17\CNB2J12345 if (parts.Length > 2 && !parts[2].Contains('&')) { return parts[2]; } return null; } private string? GetIpFromPortName(string? portName) { if (string.IsNullOrEmpty(portName)) return null; // Standard TCP/IP Port names are often "IP_192.168.1.100" if (portName.StartsWith("IP_")) { return portName.Substring(3); } // WSD ports can also contain an IP address if (portName.StartsWith("WSD-") && IPAddress.TryParse(portName.Split('/').LastOrDefault(), out _)) { return portName.Split('/').LastOrDefault(); } return null; } private string? ResolveHostnameToIp(string? hostname) { if (string.IsNullOrWhiteSpace(hostname)) return null; // Clean up hostname if it's a UNC path like \\SERVER hostname = hostname.TrimStart('\\'); try { // GetHostAddresses can return multiple IPs (IPv6, IPv4). Prioritize IPv4. var addresses = Dns.GetHostAddresses(hostname); return addresses.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork)?.ToString(); } catch (SocketException) { /* Host not found */ } return null; } private string? GetPrinterSerialViaSnmp(string ipAddress, string community = "public", int timeout = 2000) { // OID for Printer MIB v2 serial number: 1.3.6.1.2.1.43.5.1.1.17.1 byte[] oid = { 0x2b, 0x06, 0x01, 0x02, 0x01, 0x2b, 0x05, 0x01, 0x01, 0x11, 0x01 }; try { using var udpClient = new UdpClient(); udpClient.Client.SendTimeout = timeout; udpClient.Client.ReceiveTimeout = timeout; // Construct SNMP GetRequest packet var packet = new List(); packet.AddRange(new byte[] { 0x30, 0x82, 0x00, 0x00 }); // Sequence, length to be filled packet.AddRange(new byte[] { 0x02, 0x01, 0x01 }); // Version (SNMPv2c) packet.AddRange(new byte[] { 0x04, (byte)community.Length }); // Community string packet.AddRange(Encoding.ASCII.GetBytes(community)); packet.AddRange(new byte[] { 0xA0, 0x82, 0x00, 0x00 }); // GetRequest-PDU, length to be filled packet.AddRange(new byte[] { 0x02, 0x04, 0x00, 0x00, 0x00, 0x01 }); // Request ID packet.AddRange(new byte[] { 0x02, 0x01, 0x00, 0x02, 0x01, 0x00 }); // Error status, Error index packet.AddRange(new byte[] { 0x30, (byte)(oid.Length + 2) }); // Variable bindings packet.AddRange(new byte[] { 0x30, (byte)oid.Length }); // Varbind packet.AddRange(new byte[] { 0x06, (byte)oid.Length }); // OID packet.AddRange(oid); packet.AddRange(new byte[] { 0x05, 0x00 }); // Null // Update lengths // This is a simplified packet construction. A full library would be more robust. var endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), 161); udpClient.Send(packet.ToArray(), packet.Count, endpoint); byte[] received = udpClient.Receive(ref endpoint); // Basic parsing: find the serial number string in the response. // A proper ASN.1 parser would be better, but this works for simple string responses. for (int i = 0; i < received.Length - 1; i++) { if (received[i] == 0x04) // Octet String { int len = received[i + 1]; if (i + 2 + len <= received.Length && len > 0) { return Encoding.ASCII.GetString(received, i + 2, len).Trim(); } } } } catch { /* Ignore SNMP errors (timeout, host unreachable, etc.) */ } return null; } public bool HasOpticalDrive() { if (!OperatingSystem.IsWindows()) return false; try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_CDROMDrive"); return searcher.Get().Count > 0; } catch { return false; } } public List GetMonitors() { var monitors = new List(); var foundPnpDeviceIds = new HashSet(); if (!OperatingSystem.IsWindows()) return monitors; // PRIMARY METHOD: WMI (root\wmi) - Often has parsed data. try { ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\wmi", "SELECT InstanceName, SerialNumberID, UserFriendlyName FROM WmiMonitorID"); foreach (ManagementObject mo in searcher.Get()) { string model = ParseMonitorString(mo["UserFriendlyName"]); string serial = ParseMonitorString(mo["SerialNumberID"]); string pnpDeviceId = mo["InstanceName"]?.ToString()?.TrimEnd('_', '0').Trim() ?? string.Empty; if (!string.IsNullOrEmpty(pnpDeviceId)) { monitors.Add(new MonitorInfo { Model = model, SerialNumber = serial, PnPDeviceID = pnpDeviceId }); foundPnpDeviceIds.Add(pnpDeviceId); } } } catch { /* Ignore WMI errors, proceed to fallback */ } // FALLBACK METHOD: Registry EDID parsing. This is more reliable for serial numbers. try { using RegistryKey? displayKey = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Enum\DISPLAY"); if (displayKey != null) { foreach (string manufacturerId in displayKey.GetSubKeyNames()) { using RegistryKey? manufacturerKey = displayKey.OpenSubKey(manufacturerId); if (manufacturerKey == null) continue; foreach (string deviceId in manufacturerKey.GetSubKeyNames()) { string pnpDeviceId = $"DISPLAY\\{manufacturerId}\\{deviceId}"; // If we already found this monitor via WMI, skip it to avoid duplicates. if (foundPnpDeviceIds.Contains(pnpDeviceId, StringComparer.OrdinalIgnoreCase)) { continue; } using RegistryKey? deviceKey = manufacturerKey.OpenSubKey(deviceId); if (deviceKey == null) continue; using RegistryKey? deviceParamsKey = deviceKey.OpenSubKey("Device Parameters"); if (deviceParamsKey != null && deviceParamsKey.GetValue("EDID") is byte[] edid) { var (model, serial) = ParseEdid(edid); monitors.Add(new MonitorInfo { Model = model, SerialNumber = serial, PnPDeviceID = pnpDeviceId }); } } } } } catch { /* Ignore registry errors */ } return monitors.Where(m => m.Model != "N/A" || m.SerialNumber != "N/A").DistinctBy(m => m.SerialNumber).ToList(); } private string ParseMonitorString(object? value) { if (value is ushort[] chars) { return Encoding.ASCII.GetString(chars.Select(c => (byte)c).ToArray()).Trim('\0', ' ', '\r', '\n'); } return "N/A"; } private (string Model, string Serial) ParseEdid(byte[] edid) { string model = "N/A"; string serial = "N/A"; // EDID descriptor blocks start at byte 54. There are 4 blocks of 18 bytes each. for (int i = 54; i < edid.Length && i + 18 <= edid.Length; i += 18) { // Check for a descriptor block (bytes 0-2 should not be 00 00 00) if (edid[i] == 0x00 && edid[i + 1] == 0x00 && edid[i + 2] == 0x00) { byte type = edid[i + 3]; if (type == 0xFC) model = Encoding.ASCII.GetString(edid, i + 5, 13).Trim('\r', '\n', '\0', ' '); // Display Name if (type == 0xFF) serial = Encoding.ASCII.GetString(edid, i + 5, 13).Trim('\r', '\n', '\0', ' '); // Serial Number } } return (model, serial); } private string? GetWmiProperty(string wmiClass, string property) { try { var searcher = new ManagementObjectSearcher($"SELECT {property} FROM {wmiClass}"); var mo = searcher.Get().OfType().FirstOrDefault(); return mo?[property]?.ToString()?.Trim(); } catch { return null; } } private bool IsValidSerialNumber(string? serial) { return !string.IsNullOrWhiteSpace(serial) && !serial.Equals("To be filled by O.E.M.", StringComparison.OrdinalIgnoreCase) && !serial.Equals("Default string", StringComparison.OrdinalIgnoreCase) && !serial.Equals("None", StringComparison.OrdinalIgnoreCase); } public string? GetOSVersion() { if (!OperatingSystem.IsWindows()) return "N/A"; try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem"); ManagementObject? mo = searcher.Get().OfType().FirstOrDefault(); return mo?["Caption"]?.ToString()?.Trim(); } catch { return "N/A"; } } public DateTime? GetOSInstallDate() { if (!OperatingSystem.IsWindows()) return null; try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT InstallDate FROM Win32_OperatingSystem"); ManagementObject? mo = searcher.Get().OfType().FirstOrDefault(); var installDate = mo?["InstallDate"]?.ToString(); return installDate != null ? ManagementDateTimeConverter.ToDateTime(installDate) : null; } catch { return null; } } public string? GetOSLicenseKey() { if (!OperatingSystem.IsWindows()) return "N/A"; try { // This is a common method but may not work on all systems, especially with newer licensing methods. ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT OA3xOriginalProductKey FROM SoftwareLicensingService"); ManagementObject? mo = searcher.Get().OfType().FirstOrDefault(); return mo?["OA3xOriginalProductKey"]?.ToString()?.Trim(); } catch { return "N/A"; } } public List GetLocalAdmins() { var admins = new List(); if (!OperatingSystem.IsWindows()) return admins; try { using (var context = new PrincipalContext(ContextType.Machine)) { var group = GroupPrincipal.FindByIdentity(context, "Administrators"); if (group != null) { // Perform a recursive search to find all members, including those in nested groups. // Then, use DistinctBy to remove duplicates based on the unique SID. #pragma warning disable CA1416 // Validate platform compatibility var uniqueMembers = group.GetMembers(true).DistinctBy(p => p.Sid); #pragma warning restore CA1416 // Validate platform compatibility foreach (Principal principal in uniqueMembers) { var adminInfo = new LocalAdminInfo { Name = principal.Name, Sid = principal.Sid.ToString(), AccountType = principal is UserPrincipal ? "User" : "Group", Source = principal.Context.ContextType == ContextType.Machine ? "Local" : "Domain" }; if (principal is UserPrincipal user) { adminInfo.IsEnabled = user.Enabled; adminInfo.IsLockedOut = user.IsAccountLockedOut(); } admins.Add(adminInfo); } } } } catch { /* Ignore errors, e.g. on non-domain machines */ } return admins; } public (List IPAddresses, string? MACAddress) GetNetworkInfo() { try { var allIps = new List(); string? primaryMac = null; var operationalNics = NetworkInterface.GetAllNetworkInterfaces() .Where(ni => ni.OperationalStatus == OperationalStatus.Up && ni.NetworkInterfaceType != NetworkInterfaceType.Loopback && ni.NetworkInterfaceType != NetworkInterfaceType.Tunnel) .Select(ni => new { Interface = ni, IPProps = ni.GetIPProperties() }) .Where(x => !(x.Interface.Description.Contains("Virtual") || x.Interface.Name.Contains("Virtual"))); foreach (var nic in operationalNics) { allIps.AddRange(nic.IPProps.UnicastAddresses .Where(ua => ua.Address.AddressFamily == AddressFamily.InterNetwork) .Select(ua => ua.Address.ToString())); } // Find the primary MAC address from the interface with a gateway, ordered by speed var primaryNic = operationalNics.Where(n => n.IPProps.GatewayAddresses.Any()).OrderByDescending(n => n.Interface.Speed).FirstOrDefault(); primaryMac = primaryNic?.Interface.GetPhysicalAddress().ToString(); return (allIps.Distinct().ToList(), primaryMac); } catch { /* Ignore errors */ } return (new List(), "N/A"); } } }