How to reliably and quickly get the MAC address of a network card given its device instance ID
Here's one way to do it:
- Call
GetAdaptersAddresses
to get a list ofIP_ADAPTER_ADDRESSES
structs - Iterate over each adapter and get its GUID from the
AdapterName
field (I'm not sure if this behaviour is guaranteed, but all the adapters in my system have a GUID here, and the documentation says theAdapterName
is permanent) - For each adapter read the registry key from
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\<the adapter GUID>\Connection\PnPInstanceID
(if it exists) (got this idea from here; searching on Google that key seems to be well documented, so it's not likely to change) - From this key you get the device ID for the adapter (something like:
PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4
) - Do this for each adapter until you find a match. When you get your match just go back to the
IP_ADAPTER_ADDRESSES
and look at thePhysicalAddress
field - Get a beer (optional)
It wouldn't be Windows if there weren't a million ways to do something!
I wound up using SetupDiGetDeviceRegistryProperty
to read SPDRP_FRIENDLYNAME
. If that's not found, then I read SPDRP_DEVICEDESC
instead. Ultimately, this gets me a string like "VirtualBox Host-Only Ethernet Adapter #2". I then match this against the InstanceName property in the WMI NDIS classes (MSNdis_EthernetPermanentAddress
WMI class). Both properties must be read in case there are multiple adapters sharing the same driver (i.e. "#2", "#3", etc.) - if there's only one adapter then SPDRP_FRIENDLYNAME
isn't available, but if there is more than one then SPDRP_FRIENDLYNAME
is required to differentiate them.
The method makes me a little nervous because I'm comparing what seems like a localized string, and there's no documentation that I've found that guarantees what I'm doing will always work. Unfortunately, I haven't found any better ways that are documented to work, either.
A couple other alternate methods involve groveling in undocumented registry locations. One method is spencercw's method, and the other would be to read SPDRP_DRIVER
, which is the name of a subkey under HKLM\SYSTEM\CurrentControlSet\Control\Class
. Underneath the driver key, look for the Linkage\Export
value which then seems like it could be matched to the DeviceName
property of the MSNdis_EnumerateAdapter
class. But there's no documentation I could find that says these values can be legally matched. Furthermore, the only documentation I found about Linkage\Export
was from the Win2000 registry reference and explicitly said that applications shouldn't rely on it.
Another method would be to look at my original question, step 4: "SetupDiGetDeviceInterfaceDetail
for this returned device interface". The device interface path actually can be used to reconstruct the device path. Start with device interface path: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}
. Then, remove everything before the final slash, leaving you with: {28fd5409-15bd-4c06-b62f-004d3a06f852}
. Finally, prepend \Device\
to this string and match it against the WMI NDIS classes. Again, however, this seems to be undocumented and relying on an implementation detail of a device interface path.
In the end, the other methods I investigated had their own undocumented complications that sounded at least as serious as matching the SPDRP_FRIENDLYNAME
/ SPDRP_DEVICEDESC
strings. So I opted for the simpler approach, which was to just match those strings against the WMI NDIS classes.
I guess you want to get the MAC address in order to implement some sort of DRM, inventory, or classification system, since you tried to get the permanent MAC address instead of the current one.
You seem to forget that there's even an administratively super-imposed MAC address (in other words: a "forced" MAC address).
Some drivers let you do this from the Device Property page, under the Advanced tab (for example: my Marvell network adapter let me do this), while some others don't let you do that (read: they don't support that property).
However, it all ends in a Registry value: HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress
, with a REG_SZ
type.
Here you can set a different MAC address than the original one, in the form "01020304abcd" (6 bytes, plain hexadecimal, without :
separators or 0x
prefix).
After you set it, reboot the machine, and on power-up the new MAC address will have effect.
I happen to have a motherboard with two Marvell integrated NICs, and a NETGEAR USB WiFi NIC. The Marvell one supports changing the MAC address: if you set the NetworkAddress
value in the Registry, you see the new value in the driver properties page, too, and it has effect immediately, without the need to restart (if you change it from device Property Page).
Here follows the results of reading the MAC address with different methods:
GetAdaptersInfo
: new MAC addressIOCTL_NDIS_QUERY_GLOBAL_STATS
: original MAC addressMSNdis_EthernetPermanentAddress
: original MAC address
I tried adding the NetworkAddress
value in the Registry for the NETGEAR USB WiFi NIC, and the results are:
GetAdaptersInfo
: new MAC addressIOCTL_NDIS_QUERY_GLOBAL_STATS
: new MAC addressMSNdis_EthernetPermanentAddress
: new MAC address
The original MAC addres is gone.
So, in order to not be fooled by a "malicious" user, you always need to check the HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress
Registry value. If that is set, i guess it's better to not trust that Network Adapter at all, since it is up to the driver implementation to decide what will be presented to you using the different methods.
Some background for getting to that Registry key:
Microsoft documentation about the HKLM\SYSTEM\CurrentControlSet\Class key
According to the Microsoft documentation on that page,
There is a subkey for each class that is named using the GUID of the setup class
So we choose the {4D36E972-E325-11CE-BFC1-08002BE10318}
subkey (aka GUID_DEVCLASS_NET
, defined in <devguid.h>
, and further documented here)
Again, according to Microsoft documentation,
Each class subkey contains other subkeys known as software keys (or, driver keys) for each device instance of that class installed in the system. Each of these software keys is named by using a device instance ID, which is a base-10, four-digit ordinal value The xxxx part is a 4-character textual representation of a positive integer, starting from 0
So, you can traverse the subkeys up from 0000, 0001, 0002, up to the number of network adapters in your system.
The documentation stops here: I didn't find any other documentation about the different registry values, or such.
However, in each of these subkeys, you can find REG_SZ values that can help you link the GetAdaptersInfo()
, MSNdis_EthernetPermanentAddress
, Win32_NetworkAdapter
, and Device Instance ID worlds (and this answers your question).
The Registry values are:
DeviceInstanceID
: its value is, no surprise, the Device Instance IDNetCfgInstanceId
: its value is theAdapterName
member of theIP_ADAPTER_INFO
struct, returned byGetAdaptersInfo()
. It is also theGUID
member of theWin32_NetworkAdapter
WMI class.- Don't forget the
NetworkAddress
one: should a valid MAC address exist here, a driver may report it as the MAC address in use byGetAdaptersInfo()
,MSNdis_EthernetPermanentAddress
, andIOCTL_NDIS_QUERY_GLOBAL_STATS
!
Then, as you already said, the only connection between the MSNdis_EthernetPermanentAddress
WMI Class and the rest of the "world" is by its InstanceName
member. You can relate it to the Description
member of the IP_ADAPTER_INFO
struct, returned by GetAdaptersInfo()
. Although it may be a localized name, it seems to be unique for the system (For my two integrated Marvell NICs, the second one has a " #2" appended to its name).
Final note:
Said all the above, the user could choose to disable WMI...