首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 使用 WMI 取得網卡 MAC 位址與硬碟序號完整範例

     編輯:關於C語言

前言

在軟體授權綁定、裝置指紋識別、資產管理等情境中,常需取得硬體唯一識別碼。本文示範使用 `System.Management` 命名空間透過 WMI(Windows Management Instrumentation)查詢網卡 MAC 位址與硬碟卷標序號,包含完整錯誤處理與多網卡支援。

核心概念:WMI 查詢基礎

  • 命名空間:`root\CIMV2`(預設)
  • 常用類別:
    • `Win32_NetworkAdapterConfiguration` - 網路介面卡設定
    • `Win32_LogicalDisk` - 邏輯磁碟(分割區)
    • `Win32_PhysicalMedia` - 實體硬碟(需管理員權限)
  • 查詢語法:WQL(WMI Query Language),類 SQL

完整實作程式碼

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();
    }
}

專案設定需求

  1. 加入參考:右鍵專案 → 加入參考 → `System.Management`
  2. 目標框架:.NET Framework 4.x / .NET Core 3.1+ / .NET 5+ 皆支援
  3. 執行權限:查詢 `Win32_PhysicalMedia` 需管理員權限,建議在 `app.manifest` 設定 `requireAdministrator`

跨平台考量(.NET Core / .NET 5+)

WMI 僅限 Windows。跨平台專案建議:

  • Windows:使用 `System.Management`(如本文)
  • Linux:讀取 `/sys/class/net/*/address` (MAC) 與 `/sys/block/*/device/serial` (硬碟)
  • macOS:使用 `IOKit` 或 `system_profiler` 指令
  • 統一介面:建立介面 `IHardwareInfo`,各平台實作不同類別,DI 注入使用

常見問題與注意事項

問題說明解決方案
虛擬網卡干擾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 平台取得硬體資訊的標準方式。實務應用中建議:組合多項硬體特徵產生指紋、優先使用不可變的實體序號、妥善處理權限與虛擬裝置干擾。跨平台專案請採用策略模式封裝平台差異,維持核心邏輯乾淨。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved