673 lines
29 KiB
C#
673 lines
29 KiB
C#
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<ManagementObject>().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<ManagementObject>().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<ManagementObject>().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<string> GetGPUs()
|
|
{
|
|
var gpus = new List<string>();
|
|
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<StorageDeviceInfo> GetStorage()
|
|
{
|
|
var drives = new List<StorageDeviceInfo>();
|
|
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<PrinterInfo> GetPrinters()
|
|
{
|
|
var printers = new List<PrinterInfo>();
|
|
if (!OperatingSystem.IsWindows()) return printers;
|
|
|
|
var ignoredPrinterNames = new List<string>
|
|
{
|
|
"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<byte>();
|
|
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<MonitorInfo> GetMonitors()
|
|
{
|
|
var monitors = new List<MonitorInfo>();
|
|
var foundPnpDeviceIds = new HashSet<string>();
|
|
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<ManagementObject>().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<ManagementObject>().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<ManagementObject>().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<ManagementObject>().FirstOrDefault();
|
|
return mo?["OA3xOriginalProductKey"]?.ToString()?.Trim();
|
|
}
|
|
catch { return "N/A"; }
|
|
}
|
|
|
|
public List<LocalAdminInfo> GetLocalAdmins()
|
|
{
|
|
var admins = new List<LocalAdminInfo>();
|
|
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<string> IPAddresses, string? MACAddress) GetNetworkInfo()
|
|
{
|
|
try
|
|
{
|
|
var allIps = new List<string>();
|
|
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<string>(), "N/A");
|
|
}
|
|
}
|
|
} |