取得裝置唯一識別碼常用於軟體授權、資產管理、裝置追蹤。本文示範使用 WMI (System.Management) 查詢網卡 MAC 位址與硬碟卷標序號,並處理多網卡、虛擬網卡、權限等實務問題。
using System;
using System.Management;
using System.Net.NetworkInformation;
using System.Linq;
public class HardwareInfoReader
{
///
/// 取得第一張啟用的實體網卡 MAC 位址
///
public string GetMacAddress()
{
try
{
// 方法一:NetworkInformation (較簡單、無需 WMI 權限)
var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(n => n.OperationalStatus == OperationalStatus.Up
&& n.NetworkInterfaceType != NetworkInterfaceType.Loopback
&& n.NetworkInterfaceType != NetworkInterfaceType.Tunnel
&& !n.Description.Contains("Virtual")
&& !n.Description.Contains("VMware")
&& !n.Description.Contains("Hyper-V"))
.OrderBy(n => n.NetworkInterfaceType); // 優先乙太網路
foreach (var nic in nics)
{
string mac = nic.GetPhysicalAddress().ToString();
if (!string.IsNullOrEmpty(mac) && mac != "000000000000")
return FormatMacAddress(mac);
}
}
catch { }
// 方法二:WMI 備用
try
{
using (var mc = new ManagementClass("Win32_NetworkAdapterConfiguration"))
using (var moc = mc.GetInstances())
{
foreach (ManagementObject mo in moc)
{
if ((bool)mo["IPEnabled"])
{
string mac = mo["MacAddress"]?.ToString();
if (!string.IsNullOrEmpty(mac))
return mac.Replace(":", "-").ToUpper();
}
}
}
}
catch { }
return "UNKNOWN";
}
private string FormatMacAddress(string mac)
{
if (mac.Length == 12)
return string.Join("-", Enumerable.Range(0, 6).Select(i => mac.Substring(i * 2, 2)));
return mac;
}
///
/// 取得系統碟 (C:) 卷標序號
///
public string GetDiskVolumeSerialNumber(string driveLetter = "C")
{
try
{
// 方法一:WMI 查詢
using (var mo = new ManagementObject($"win32_logicaldisk.deviceid=\"{driveLetter}:\""))
{
mo.Get();
return mo.GetPropertyValue("VolumeSerialNumber")?.ToString()?.ToUpper() ?? "UNKNOWN";
}
}
catch { }
try
{
// 方法二:Kernel32 GetVolumeInformation (P/Invoke)
return GetVolumeSerialNumberPInvoke(driveLetter);
}
catch { }
return "UNKNOWN";
}
[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool GetVolumeInformation(
string lpRootPathName,
System.Text.StringBuilder lpVolumeNameBuffer,
int nVolumeNameSize,
out uint lpVolumeSerialNumber,
out uint lpMaximumComponentLength,
out uint lpFileSystemFlags,
System.Text.StringBuilder lpFileSystemNameBuffer,
int nFileSystemNameSize);
private string GetVolumeSerialNumberPInvoke(string driveLetter)
{
uint serialNumber = 0;
uint maxCompLen = 0;
uint fsFlags = 0;
var volName = new System.Text.StringBuilder(256);
var fsName = new System.Text.StringBuilder(256);
bool result = GetVolumeInformation(
$"{driveLetter}:\\", volName, volName.Capacity,
out serialNumber, out maxCompLen, out fsFlags, fsName, fsName.Capacity);
return result ? serialNumber.ToString("X8") : "UNKNOWN";
}
///
/// 取得 CPU ID
///
public string GetCpuId()
{
try
{
using (var mc = new ManagementClass("Win32_Processor"))
using (var moc = mc.GetInstances())
{
foreach (ManagementObject mo in moc)
{
return mo["ProcessorId"]?.ToString()?.Trim() ?? "UNKNOWN";
}
}
}
catch { }
return "UNKNOWN";
}
///
/// 取得主機板序號
///
public string GetMotherboardSerial()
{
try
{
using (var mc = new ManagementClass("Win32_BaseBoard"))
using (var moc = mc.GetInstances())
{
foreach (ManagementObject mo in moc)
{
return mo["SerialNumber"]?.ToString()?.Trim() ?? "UNKNOWN";
}
}
}
catch { }
return "UNKNOWN";
}
///
/// 取得 BIOS 序號
///
public string GetBiosSerial()
{
try
{
using (var mc = new ManagementClass("Win32_BIOS"))
using (var moc = mc.GetInstances())
{
foreach (ManagementObject mo in moc)
{
return mo["SerialNumber"]?.ToString()?.Trim() ?? "UNKNOWN";
}
}
}
catch { }
return "UNKNOWN";
}
}
public class DeviceFingerprint
{
private readonly HardwareInfoReader _reader = new HardwareInfoReader();
public string GenerateFingerprint()
{
var parts = new[]
{
_reader.GetCpuId(),
_reader.GetMotherboardSerial(),
_reader.GetBiosSerial(),
_reader.GetDiskVolumeSerialNumber(),
_reader.GetMacAddress()
};
// 過濾未知值,組合並雜湊
string combined = string.Join("|", parts.Where(p => p != "UNKNOWN"));
return ComputeHash(combined);
}
private string ComputeHash(string input)
{
using (var sha256 = System.Security.Cryptography.SHA256.Create())
{
byte[] hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input));
return BitConverter.ToString(hash).Replace("-", "").Substring(0, 16);
}
}
}
public class LicenseValidator
{
private readonly DeviceFingerprint _fingerprint = new DeviceFingerprint();
private readonly string _expectedFingerprint; // 從授權檔/伺服器取得
public LicenseValidator(string expectedFingerprint)
{
_expectedFingerprint = expectedFingerprint;
}
public bool Validate()
{
string current = _fingerprint.GenerateFingerprint();
return string.Equals(current, _expectedFingerprint, StringComparison.OrdinalIgnoreCase);
}
public LicenseInfo GetLicenseInfo()
{
return new LicenseInfo
{
IsValid = Validate(),
CurrentFingerprint = _fingerprint.GenerateFingerprint(),
ExpectedFingerprint = _expectedFingerprint,
HardwareDetails = new HardwareInfoReader().GetAllDetails()
};
}
}
public class LicenseInfo
{
public bool IsValid { get; set; }
public string CurrentFingerprint { get; set; }
public string ExpectedFingerprint { get; set; }
public HardwareDetails HardwareDetails { get; set; }
}
public class HardwareDetails
{
public string CpuId { get; set; }
public string MotherboardSerial { get; set; }
public string BiosSerial { get; set; }
public string DiskSerial { get; set; }
public string MacAddress { get; set; }
}
| 項目 | 需求 | 解決方案 |
|---|---|---|
| WMI 查詢 | 管理員權限或 WMI 權限 | 應用程式清單設定 requireAdministrator,或設定 WMI 命名空間權限 |
| ManagementClass | .NET Framework 完整支援 | .NET Core 需 NuGet: System.Management |
| 虛擬機器 | MAC/序號可能相同 | 組合多項硬體資訊,或使用雲端驗證 |
| 網卡變更 | 更換網卡導致 MAC 變更 | 授權綁定多項硬體,允許部分變更 |
| 硬碟重新格式化 | 卷標序號改變 | 改用硬碟實體序號 (Win32_DiskDrive.SerialNumber) |
public string GetPhysicalDiskSerial()
{
try
{
using (var mc = new ManagementClass("Win32_DiskDrive"))
using (var moc = mc.GetInstances())
{
foreach (ManagementObject mo in moc)
{
// 取得第一顆實體磁碟序號
return mo["SerialNumber"]?.ToString()?.Trim() ?? "UNKNOWN";
}
}
}
catch { }
return "UNKNOWN";
}
// 純 .NET Core 跨平台取得 MAC
public string GetMacAddressCrossPlatform()
{
var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(n => n.OperationalStatus == OperationalStatus.Up
&& n.NetworkInterfaceType != NetworkInterfaceType.Loopback);
foreach (var nic in nics)
{
var mac = nic.GetPhysicalAddress().ToString();
if (!string.IsNullOrEmpty(mac) && mac != "000000000000")
return FormatMacAddress(mac);
}
return "UNKNOWN";
}
// 取得機器名稱 (跨平台)
public string GetMachineName() => Environment.MachineName;
// 取得作業系統資訊
public string GetOsInfo() => $"{Environment.OSVersion} {RuntimeInformation.OSDescription}";
硬體資訊讀取首選 NetworkInterface 取得 MAC (無需 WMI 權限、跨平台),次選 WMI 查詢硬碟/CPU/主機板序號。實務授權建議組合多項硬體特徵產生指紋,並允許部分硬體變更容錯。注意虛擬環境、容器化部署下的硬體資訊可能不唯一,生產環境應搭配雲端驗證機制。