Given a device instance ID for a network card, I would like to know its MAC address. Example device instance ID on my system for integrated Intel Gigabit card:
PCI\VEN_8086&DEV_10CC&SUBSYS_00008086&REV_00\3&33FD14CA&0&C8
So far, the algorithm I have used works as follows:
SetupDiGetClassDevs
with DIGCF_DEVICEINTERFACE
.SetupDiEnumDeviceInfo
to get the returned device in a SP_DEVINFO_DATA
.SetupDiEnumDeviceInterfaces
with GUID_NDIS_LAN_CLASS
to get a device interface.SetupDiGetDeviceInterfaceDetail
for this returned device interface. This gets us the device path as a string: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}
CreateFile
using the result from #4.DeviceIoControl
with IOCTL_NDIS_QUERY_GLOBAL_STATS
and OID of OID_802_3_PERMANENT_ADDRESS
to get the MAC address.This usually works, and has been used successfully on quite a large number of machines. However, it appears that a very select few machines have network drivers that aren't responding properly to the DeviceIoControl
request in step #6; the problem persists even after updating network card drivers to the latest. These are newer, Windows 7-based computers. Specifically, DeviceIoControl
completes successfully, but returns zero bytes instead of the expected six bytes containing the MAC address.
A clue seems to be on the MSDN page for IOCTL_NDIS_QUERY_GLOBAL_STATS
:
This IOCTL will be deprecated in later operating system releases. You should use WMI interfaces to query miniport driver information. For more information see, NDIS Support for WMI.
-- perhaps newer network card drivers are no longer implementing this IOCTL?
So, how should I get this working? Is it possible there's an oversight in my approach and I'm doing something slightly wrong? Or do I need to take a much more different approach? Some alternate approaches seem to include:
Win32_NetworkAdapter
WMI class: provides needed information but rejected due to horrible performance. See Fast replacement for Win32_NetworkAdapter WMI class for getting MAC address of local computer
MSNdis_EthernetPermanentAddress
WMI class: appears to be the WMI replacement for IOCTL_NDIS_QUERY_GLOBAL_STATS
and queries the OID directly from the driver - and this one works on the troublesome network driver. Unfortunately, the returned class instances only provide the MAC address and the InstanceName
, which is a localized string like Intel(R) 82567LM-2 Gigabit Network Connection
. Querying MSNdis_EnumerateAdapter
yields a list which relates the InstanceName
to a DeviceName
, like \DEVICE\{28FD5409-15BD-4C06-B62F-004D3A06F852}
. I'm not sure how to go from the DeviceName
to the plug-and-play device instance ID (PCI\VEN_8086......
).GetAdaptersAddresses
or GetAdaptersInfo
(deprecated). The only non-localized identifier I can find in the return value is the adapter name, which is a string like {28FD5409-15BD-4C06-B62F-004D3A06F852}
- same as the DeviceName
returned by the WMI NDIS classes. So again, I can't figure out how to relate it to the device instance ID. I'm not sure if it would work 100% of the time either - e.g. for adapters without TCP/IP protocol configured.It seems like if I could find a way to get the "GUID" for the card from the device instance ID, I'd be well on my way with one of the remaining two ways of doing things. But I haven't figured out how yet. Otherwise, the WMI NDIS approach would seem most promising.
Getting a list of network cards and MAC addresses is easy, and there are several ways of doing it. Doing it in a fast way that lets me relate it to the device instance ID is apparently hard...
EDIT: Sample code of the IOCTL call if it helps anyone (ignore the leaked hFile handle):
HANDLE hFile = CreateFile(dosDevice.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
DWORD err = GetLastError();
wcout << "GetMACAddress: CreateFile on " << dosDevice << " failed." << endl;
return MACAddress();
}
BYTE address[6];
DWORD oid = OID_802_3_PERMANENT_ADDRESS, returned = 0;
//this fails too: DWORD oid = OID_802_3_CURRENT_ADDRESS, returned = 0;
if (!DeviceIoControl(hFile, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), address, 6, &returned, NULL)) {
DWORD err = GetLastError();
wcout << "GetMACAddress: DeviceIoControl on " << dosDevice << " failed." << endl;
return MACAddress();
}
if (returned != 6) {
wcout << "GetMACAddress: invalid address length of " << returned << "." << endl;
return MACAddress();
}
The code fails, printing:
GetMACAddress: invalid address length of 0.
So the DeviceIoControl returns non-zero indicating success, but then returns zero bytes.
Windows ComputersType ipconfig /all at the command prompt to check the network card settings. The MAC address and IP address are listed under the appropriate adapter as Physical Address and IPv4 Address. Note: If you do not see the Physical Address and IPv4 Address, type ~ cd at the command prompt, then repeat step 3.
Manufacturers assign a MAC address to a network adapter when it is produced. It is hardwired or hard-coded onto your computer's NIC and is unique to it. Something called the Address Resolution Protocol (ARP) translates an IP address into a MAC address.
Here's one way to do it:
GetAdaptersAddresses
to get a list of IP_ADAPTER_ADDRESSES
structsAdapterName
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 the AdapterName
is permanent)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)PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4
)IP_ADAPTER_ADDRESSES
and look at the PhysicalAddress
fieldIt 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With