HardwareIds.NET 2.2.0

dotnet add package HardwareIds.NET --version 2.2.0
                    
NuGet\Install-Package HardwareIds.NET -Version 2.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="HardwareIds.NET" Version="2.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HardwareIds.NET" Version="2.2.0" />
                    
Directory.Packages.props
<PackageReference Include="HardwareIds.NET" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add HardwareIds.NET --version 2.2.0
                    
#r "nuget: HardwareIds.NET, 2.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package HardwareIds.NET@2.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=HardwareIds.NET&version=2.2.0
                    
Install as a Cake Addin
#tool nuget:?package=HardwareIds.NET&version=2.2.0
                    
Install as a Cake Tool

<h1 align="center">HardwareIds.NET</h1>

<p align="center"> Fingerprint a Windows machine in about 10 milliseconds, straight from the Windows APIs, with no WMI and no dependencies. </p>

<p align="center"> <a href="https://www.nuget.org/packages/HardwareIds.NET"><img alt="NuGet version" src="https://img.shields.io/nuget/v/HardwareIds.NET?logo=nuget&label=NuGet"></a> <a href="https://www.nuget.org/packages/HardwareIds.NET"><img alt="NuGet downloads" src="https://img.shields.io/nuget/dt/HardwareIds.NET?logo=nuget&label=Downloads"></a> <a href="https://github.com/BerkanYildiz/HardwareIds.NET/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/BerkanYildiz/HardwareIds.NET/actions/workflows/ci.yml/badge.svg"></a> <a href="https://github.com/BerkanYildiz/HardwareIds.NET/actions/workflows/publish.yml"><img alt="Publish" src="https://github.com/BerkanYildiz/HardwareIds.NET/actions/workflows/publish.yml/badge.svg"></a> <a href="LICENSE"><img alt="License" src="https://img.shields.io/github/license/BerkanYildiz/HardwareIds.NET"></a> <img alt="Targets" src="https://img.shields.io/badge/.NET-10.0%20%7C%20Framework%204.8-512BD4?logo=dotnet"> </p>

HardwareIds.NET enumerates every hardware component of the computer it runs on and collects the identifiers that survive re-installs and component swaps: serial numbers, MAC addresses, firmware and partition table GUIDs, EDID blocks, SMBIOS data, the Windows installation identifiers, and optionally the Wi-Fi networks and LAN devices around the machine, so a user can be recognised even on an entirely new computer sitting in the same place.

Highlights

  • Fast. A full hardware scan takes about 10 ms. Everything is read from the Windows APIs directly: the SMBIOS table, storage and NDIS IOCTLs, the PnP configuration manager, the registry, the print spooler, the display configuration API, the battery and Bluetooth APIs and WlanAPI. No WMI, no winmgmt service, no WmiPrvSE host.
  • Dependency-free. No third-party packages. Only System.Text.Json on .NET Framework, which is part of .NET 10.
  • No elevation needed. Every collector works from a standard user account.
  • WMI-compatible values. The historical fields hold the exact strings WMI reports, so identifiers collected by earlier versions keep matching.
  • Serializable. The result is a plain object with System.Text.Json attributes; serialize it and store it as is.
  • Tested. 130+ unit and integration tests, the latter checking every collector against WMI as an independent oracle.
  • C and C++ too. HardwareIds.Cpp is a native port that writes the very same JSON, byte for byte.

What is collected

Section Source Identifiers
disks Storage IOCTLs, NVMe / ATA identify, SCSI VPD page 0x83, PnP Model, descriptor serial, NVMe serial / EUI-64 / NGUID / FGUID, ATA serial / WWN, VPD identifiers, Windows DUID, firmware, partition table GUID, instance path
volumes Volume management API Volume GUID path, drive letter, NTFS/FAT serial
network_adapters GetIfTable2, registry, PnP Current and permanent MAC, interface GUID, driver service, install date, instance path
bluetooth_radios Bluetooth API Radio address, name, manufacturer, class of device
baseboards, motherboards, chassis, bios_firmwares, smbios_tables SMBIOS (GetSystemFirmwareTable) Board serial, system UUID, chassis serial and asset tag, BIOS version, hash of the whole table
processors SMBIOS, CPUID, registry Model, ProcessorId, socket, cores and threads
memory_sticks SMBIOS Manufacturer, part number, serial, capacity, speed, slot
batteries Battery IOCTLs Serial, unique ID, chemistry, capacities, manufacture date
monitors EDID (PnP registry) Manufacturer, product code, serial, name, EDID hash, manufacture week and year, instance path
video_controllers PnP, display configuration API Name, driver version and date, current mode, instance path
printers Print spooler Name, port, location, resolution
users NetUserEnum Local accounts with their SIDs
operating_systems Registry, branding API Edition, version, product ID, install date and time, machine GUID, hardware profile GUID, machine SID
wifis (optional) WlanAPI scan SSID, BSSID, signal, channel, band
routers (optional) Neighbour cache, ARP sweep Gateways with their MAC, DNS and DHCP servers, every device on the LAN
network_signatures Registry (elevated only) Networks the machine connected to, with their gateway MAC

Installation

dotnet add package HardwareIds.NET
PM> Install-Package HardwareIds.NET

Requirements: Windows, and .NET 10 or .NET Framework 4.8.

Usage

using HardwareIds.NET;

// Hardware only: about 10 ms.
var Hwid = HardwareIds.GetHwid();

Console.WriteLine(Hwid.Motherboard?.UUID);
Console.WriteLine(Hwid.Disk?.NvmeSerial ?? Hwid.Disk?.AtaSerial ?? Hwid.Disk?.SerialNumber);
Console.WriteLine(Hwid.OperatingSystem?.MachineGuid);
// With the network probes: a few seconds, bound by the Wi-Fi scan.
var Hwid = await HardwareIds.GetHwidAsync(new HardwareIdsConfig
{
    ScanNeighborEndpoints = true,                                 // Wi-Fi networks around the computer
    DurationOfNetworkScan = TimeSpan.FromSeconds(3),              // how long to wait for the Wi-Fi scan
    ScanLocalNetworkDevices = true,                               // devices on the LAN, with their MAC addresses
    DurationOfLocalNetworkScan = TimeSpan.FromSeconds(1),         // how long to wait for the devices to answer
}, cancellationToken);

foreach (var Wifi in Hwid.Wifis.OrderByDescending(T => T.Strength))
    Console.WriteLine($"{Wifi.Ssid,-32} {Wifi.Bssid}  {Wifi.Strength} dBm  channel {Wifi.Channel}");

foreach (var Device in Hwid.Router?.NetworkDevices ?? [])
    Console.WriteLine($"{Device.Ip,-16} {Device.MacAddress}");
// The result serializes as is, with stable snake_case property names.
var Json = JsonSerializer.Serialize(Hwid);

Every section is a list, and each has a convenience getter for its first entry (Hwid.Disk, Hwid.Processor, Hwid.OperatingSystem, ...). Collectors never throw: a component that cannot be read simply yields an empty list.

A disk, for example

{
  "id": 1,
  "interface": "SCSI",
  "model": "Samsung SSD 980 PRO 2TB",
  "serial_number": "0025_384A_471F_58A7.",
  "capacity": "1863 GB",
  "partitions": 3,
  "firmware": "5B2QGXA7",
  "world_wide_name": "0025384A471F58A7",
  "disk_guid": "95002274-501c-4590-8f6c-6b45c75a3a0c",
  "nvme_serial": "S69ENC5C753184B",
  "nvme_eui64": "0025384A471F58A7",
  "vpd_scsi_name": "eui.0025384A471F58A7",
  "duid": "3f1c…"
}

A drive legitimately reports different serial numbers depending on the layer asked. serial_number is the value WMI reports (for NVMe drives, Windows derives it from the EUI-64), while nvme_serial or ata_serial is the serial printed on the label. All of them are kept, each in its own field.

C and C++

HardwareIds.Cpp is a port of the library to C++20 for native programs. It runs the same collectors with the same rules and serializes the result to the JSON JsonSerializer.Serialize(Hwid) produces, byte for byte, so a snapshot taken by a native program can be compared directly with one taken from .NET. CI checks this on every push by diffing the output of both libraries on the runner. It needs MSVC and CMake 3.21 or later, and nothing else.

Adding it to a CMake project

include(FetchContent)
FetchContent_Declare(HardwareIds
    GIT_REPOSITORY https://github.com/BerkanYildiz/HardwareIds.NET.git
    GIT_TAG        master                # or a release tag
    SOURCE_SUBDIR  HardwareIds.Cpp)
FetchContent_MakeAvailable(HardwareIds)

target_link_libraries(MyApp PRIVATE HardwareIds::HardwareIds)   # static library: C++ and C API

add_subdirectory(path/to/HardwareIds.Cpp) works the same way. Each release also attaches HardwareIds.Cpp-<version>-win-x64.zip, a prebuilt install tree for find_package(HardwareIds CONFIG REQUIRED): it holds HardwareIds::Shared (HardwareIds.dll, the C API, usable from any configuration and any compiler) and HardwareIds::HardwareIds (the static library, built in Release with /MD).

C++

#include <HardwareIds/HardwareIds.hpp>

// Hardware only: a few milliseconds.
auto Hwid = HardwareIds::GetHwid();

std::string Json = HardwareIds::ToJson(Hwid);                                     // JsonSerializer.Serialize(Hwid)
std::string Pretty = HardwareIds::ToJson(Hwid, HardwareIds::JsonFormat::Indented); // ... with WriteIndented = true

for (const auto& Disk : Hwid.Disks)
    std::wcout << Disk.Model.value_or(L"?") << L"  " << Disk.NvmeSerial.value_or(Disk.SerialNumber.value_or(L"")) << L'\n';
// With the network probes. Requesting a stop ends the scans early, like the .NET cancellation token.
HardwareIds::HardwareIdsConfig Config;
Config.ScanNeighborEndpoints = true;
Config.DurationOfNetworkScan = std::chrono::seconds(3);
Config.ScanLocalNetworkDevices = true;

std::stop_source Stop;
auto Hwid = HardwareIds::GetHwid(Config, Stop.get_token());

The structures mirror the .NET ones: the same sections and fields, strings as std::wstring, and missing values as std::nullopt (JSON null).

C

#include <HardwareIds/HardwareIds.h>

HardwareIds_Options Options = { 0 };            /* or pass NULL: hardware only, compact JSON */
Options.ScanLocalNetworkDevices = 1;

char* Json = HardwareIds_GetSnapshotJson(&Options);

if (Json != NULL)
{
    puts(Json);
    HardwareIds_Free(Json);
}

Link against HardwareIds.lib and ship HardwareIds.dll next to the program, or link the static library and define HARDWAREIDS_STATIC (the CMake target defines it for you).

Snapshot tool

HardwareIdsSnapshot prints a snapshot from the command line:

HardwareIdsSnapshot --indented --wifi=3 --lan --output snapshot.json

Notes on the identifiers

  • ProcessorId is the CPUID signature the firmware records in SMBIOS, as WMI reports it: identical for every CPU of the same model and stepping, not unique per chip, and all zeros under some hypervisors (Hyper-V, Azure).
  • network_signatures needs an elevated process; it is empty otherwise.
  • routers lists only devices inside the subnets of the interface and never devices answering with a gateway's MAC, which filters out the proxy-ARP artefacts of statically routed hosts.
  • The Windows product key is deliberately not collected.

Building and testing

dotnet build
dotnet test

The test project runs on both target frameworks. Unit tests exercise the SMBIOS, EDID, NVMe, ATA and SCSI parsers on synthetic data; integration tests run every collector on the local machine and compare the results with WMI. Tests that need hardware or privileges the machine lacks (Wi-Fi, a battery, a Bluetooth radio, monitor EDID, elevation) are skipped, not failed.

The C++ library has its own tests, and a script that checks both libraries print the same snapshot:

cmake -S HardwareIds.Cpp -B HardwareIds.Cpp/build
cmake --build HardwareIds.Cpp/build --config Release
ctest --test-dir HardwareIds.Cpp/build --build-config Release
./.github/scripts/parity.ps1 -Snapshot HardwareIds.Cpp/build/Release/HardwareIdsSnapshot.exe

Releasing

Pushing a version tag publishes the package:

git tag v2.1.0
git push origin v2.1.0

The Publish workflow builds and tests the solution, packs the library with the tag as its version, pushes it to NuGet.org and GitHub Packages, and attaches the packages to a GitHub release with generated notes, along with the zip of the C++ library. A tag with a pre-release suffix (v2.1.0-beta.1) produces a pre-release.

No secret is involved: NuGet.org is reached through Trusted Publishing, which exchanges the workflow's OIDC token for a one-hour API key, and GitHub Packages uses the built-in token. The nuget.org account owning the package needs one Trusted Publishing policy (profile menu, Trusted Publishing):

Field Value
Package Owner the nuget.org profile that owns HardwareIds.NET
CI/CD Provider GitHub Actions
Repository Owner BerkanYildiz
Repository HardwareIds.NET
Workflow File publish.yml
Environment leave empty
Scopes Push, new packages and package versions
Glob Patterns and Packages HardwareIds.NET

License

MIT. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Framework net48 is compatible.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.2.0 104 10/6/2026
2.1.1 112 9/23/2026
2.1.0 105 9/23/2026
2.0.0 111 9/16/2026
1.1.12 377 9/7/2025
1.1.11 216 9/7/2025
1.1.10 317 5/11/2025
1.1.9 634 5/6/2024
1.1.8 369 3/27/2024
1.1.7 281 2/19/2024
1.1.6 461 6/22/2023
1.1.5 492 2/21/2023
1.1.4 850 3/7/2022
1.1.3 566 8/20/2021
1.1.2 548 8/14/2021
1.1.1 544 8/11/2021
1.1.0 541 8/10/2021
1.0.0 594 6/7/2021

Hardware information is now read directly from the Windows APIs (SMBIOS, storage and NDIS IOCTLs, PnP, EDID, registry, spooler, WlanAPI) instead of WMI and wrapper libraries: a scan takes about 10 ms instead of seconds, and the library has no third-party dependencies anymore. New identifiers: per-disk NVMe, ATA, SCSI VPD and DUID fields, firmware, WWN and partition table GUID; PnP instance paths; monitor EDID hash and manufacture date; chassis; Bluetooth radios; batteries; machine GUID, hardware profile GUID, install time and machine SID. Public API unchanged (additive); targets .NET 10 and .NET Framework 4.8.