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:
- Call
SetupDiGetClassDevs
withDIGCF_DEVICEINTERFACE
. - Call
SetupDiEnumDeviceInfo
to get the returned device in aSP_DEVINFO_DATA
. - Call
SetupDiEnumDeviceInterfaces
withGUID_NDIS_LAN_CLASS
to get a device interface. - Call
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}
- At this point we have an address to the network card driver's interface. Open it with
CreateFile
using the result from #4. - Call
DeviceIoControl
withIOCTL_NDIS_QUERY_GLOBAL_STATS
and OID ofOID_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:
- Query
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 - Query
MSNdis_EthernetPermanentAddress
WMI class: appears to be the WMI replacement forIOCTL_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 theInstanceName
, which is a localized string likeIntel(R) 82567LM-2 Gigabit Network Connection
. QueryingMSNdis_EnumerateAdapter
yields a list which relates theInstanceName
to aDeviceName
, like\DEVICE\{28FD5409-15BD-4C06-B62F-004D3A06F852}
. I'm not sure how to go from theDeviceName
to the plug-and-play device instance ID (PCI\VEN_8086......
). - Call
GetAdaptersAddresses
orGetAdaptersInfo
(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 theDeviceName
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. - NetBIOS method: requires specific protocols to be set up on the card so won't work 100% of time. Generally seems hack-ish, and not a way to relate to device instance ID anyway that I know of. I'd reject this approach.
- UUID generation method: rejected for reasons I won't elaborate on here.
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.