在軟體授權綁定、裝置指紋識別、資產管理等情境中,常需取得硬體唯一識別碼。本文示範使用 `System.Management` 命名空間透過 WMI(Windows Management Instrumentation)查詢網卡 MAC 位址與硬碟卷標序號,包含完整錯誤處理與多網卡支援。
using System;
using System.Collections.Generic;
using System.Management;
using System.Linq;
public static class HardwareInfo
{
///
/// 取得所有啟用 IP 的網卡 MAC 位址
///
public static List GetMacAddresses()
{
var macList = new List();
try
{
using (var searcher = new ManagementObjectSearcher("SELECT MACAddress, IPEnabled FROM Win32_NetworkAdapterConfiguration"))
{
foreach (ManagementObject obj in searcher.Get())
{
bool ipEnabled = (bool)(obj["IPEnabled"] ?? false);
if (ipEnabled)
{
string mac = obj["MACAddress"]?.ToString();
if (!string.IsNullOrWhiteSpace(mac))
{
macList.Add(mac.Replace(":", "").ToUpper());
}
}
}
}
}
catch (ManagementException ex)
{
Console.WriteLine($"WMI 查詢錯誤: {ex.Message}");
}
catch (System.UnauthorizedAccessException)
{
Console.WriteLine("需要管理員權限才能存取 WMI");
}
return macList;
}
///
/// 取得指定磁碟機的卷標序號(如 C:)
///
public static string GetVolumeSerialNumber(string driveLetter = "C")
{
try
{
string wql = $"SELECT VolumeSerialNumber FROM Win32_LogicalDisk WHERE DeviceID = '{driveLetter}:'";
using (var searcher = new ManagementObjectSearcher(wql))
{
foreach (ManagementObject disk in searcher.Get())
{
return disk["VolumeSerialNumber"]?.ToString()?.ToUpper() ?? "";
}
}
}
catch (Exception ex)
{
Console.WriteLine($"取得磁碟序號失敗: {ex.Message}");
}
return "";
}
///
/// 取得實體硬碟序號(需管理員權限,較唯一)
///
public static List GetPhysicalDiskSerialNumbers()
{
var serials = new List();
try
{
using (var searcher = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_PhysicalMedia"))
{
foreach (ManagementObject disk in searcher.Get())
{
string sn = disk["SerialNumber"]?.ToString()?.Trim();
if (!string.IsNullOrWhiteSpace(sn))
serials.Add(sn);
}
}
}
catch { /* 忽略權限不足等錯誤 */ }
return serials;
}
///
/// 產生裝置指紋(組合多個硬體資訊)
///
public static string GenerateDeviceFingerprint()
{
var parts = new List();
// 第一優先:實體硬碟序號
var diskSerials = GetPhysicalDiskSerialNumbers();
if (diskSerials.Count > 0)
parts.AddRange(diskSerials);
else
parts.Add(GetVolumeSerialNumber("C")); // 退而求其次用卷標序號
// 加入第一張實體網卡 MAC
var macs = GetMacAddresses();
if (macs.Count > 0)
parts.Add(macs[0]);
return string.Join("|", parts.Where(p => !string.IsNullOrEmpty(p)));
}
}
// 使用範例
class Program
{
static void Main()
{
Console.WriteLine("=== 網卡 MAC 位址 ===");
foreach (var mac in HardwareInfo.GetMacAddresses())
{
Console.WriteLine(mac);
}
Console.WriteLine("\n=== C: 磁碟卷標序號 ===");
Console.WriteLine(HardwareInfo.GetVolumeSerialNumber("C"));
Console.WriteLine("\n=== 實體硬碟序號(需管理員) ===");
foreach (var sn in HardwareInfo.GetPhysicalDiskSerialNumbers())
{
Console.WriteLine(sn);
}
Console.WriteLine("\n=== 裝置指紋 ===");
Console.WriteLine(HardwareInfo.GenerateDeviceFingerprint());
Console.ReadKey();
}
}
WMI 僅限 Windows。跨平台專案建議:
| 問題 | 說明 | 解決方案 |
|---|---|---|
| 虛擬網卡干擾 | VMware、VirtualBox、Hyper-V 建立虛擬網卡 | 過濾 `Description` 不含 Virtual/VMware,或只取實體網卡 |
| MAC 位址可變更 | 網卡驅動程式允許修改 MAC | 不作為唯一授權依據,需組合多項硬體資訊 |
| 卷標序號重新格式化會變 | 重新格式化分割區會產生新序號 | 優先使用實體硬碟序號 |
| WMI 查詢緩慢 | 首次查詢需載入 WMI 儲存庫 | 快取結果、非同步查詢、限制查詢範圍 |
| 權限不足例外 | 標準使用者無法存取部分 WMI 類別 | try-catch 捕獲、降級使用可用資訊、需求管理員權限 |
public static class HardwareInfoCache
{
private static readonly Lazy _fingerprint = new Lazy(HardwareInfo.GenerateDeviceFingerprint);
public static string DeviceFingerprint => _fingerprint.Value;
// 若需重新整理(如插拔硬體),可加入 Reset 方法
// 但 Lazy 不支援重設,改用 ConcurrentDictionary 或自訂快取邏輯
}
透過 `System.Management` 存取 WMI 是 Windows 平台取得硬體資訊的標準方式。實務應用中建議:組合多項硬體特徵產生指紋、優先使用不可變的實體序號、妥善處理權限與虛擬裝置干擾。跨平台專案請採用策略模式封裝平台差異,維持核心邏輯乾淨。