TL;DR version of this question: WMI Win32_NetworkAdapter class contains information I need, but is too slow. What's a faster method of getting information for the MACAddress, ConfigManagerErrorCode, and PNPDeviceID columns on Windows?
I need to retrieve information for attached network adapters so I can get a MAC address to uniquely identify the local Microsoft Windows computer. The WMI Win32_NetworkAdapter class seems to have the information I'm looking for. The MACAddress, ConfigManagerErrorCode, and PNPDeviceID columns are the only ones I really need:
My plan was to filter out non-physical devices using PNPDeviceID. Then I would use the MACAddress column on any remaining table entries (saving the address to a cache). When the device is disabled (as possibly indicated by a non-zero ConfigManagerErrorCode) and the MACAddress is null, I can use a previously-seen MACAddress for that device from my cache.
You can see the contents of this table on my Windows 7 computer. You can see there's tons of junk in there, but only one entry with a "PCI" PNPDeviceID.
wmic:root\cli>NIC GET Caption, ConfigManagerErrorCode, MACAddress, PNPDeviceID
Caption ConfigManagerErrorCode MACAddress PNPDeviceID
[00000000] WAN Miniport (SSTP) 0 ROOT\MS_SSTPMINIPORT\0000
[00000001] WAN Miniport (IKEv2) 0 ROOT\MS_AGILEVPNMINIPORT\0000
[00000002] WAN Miniport (L2TP) 0 ROOT\MS_L2TPMINIPORT\0000
[00000003] WAN Miniport (PPTP) 0 ROOT\MS_PPTPMINIPORT\0000
[00000004] WAN Miniport (PPPOE) 0 ROOT\MS_PPPOEMINIPORT\0000
[00000005] WAN Miniport (IPv6) 0 ROOT\MS_NDISWANIPV6\0000
[00000006] WAN Miniport (Network Monitor) 0 ROOT\MS_NDISWANBH\0000
[00000007] Intel(R) 82567LM-2 Gigabit Network Connection 0 00:1C:C0:B0:C4:89 PCI\VEN_8086&DEV_10CC&SUBSYS_00008086&REV_00\3&33FD14CA&0&C8
[00000008] WAN Miniport (IP) 0 ROOT\MS_NDISWANIP\0000
[00000009] Microsoft ISATAP Adapter 0 ROOT\*ISATAP\0000
[00000010] RAS Async Adapter 0 20:41:53:59:4E:FF SW\{EEAB7790-C514-11D1-B42B-00805FC1270E}\ASYNCMAC
[00000011] Microsoft Teredo Tunneling Adapter 0 ROOT\*TEREDO\0000
[00000012] VirtualBox Bridged Networking Driver Miniport 0 00:1C:C0:B0:C4:89 ROOT\SUN_VBOXNETFLTMP\0000
[00000013] VirtualBox Host-Only Ethernet Adapter 0 08:00:27:00:C4:A1 ROOT\NET\0000
[00000014] Microsoft ISATAP Adapter 0 ROOT\*ISATAP\0001
[00000015] VMware Virtual Ethernet Adapter for VMnet1 0 00:50:56:C0:00:01 ROOT\VMWARE\0000
[00000016] Microsoft ISATAP Adapter 0 ROOT\*ISATAP\0002
[00000017] VMware Virtual Ethernet Adapter for VMnet8 0 00:50:56:C0:00:08 ROOT\VMWARE\0001
[00000018] Microsoft ISATAP Adapter 0 ROOT\*ISATAP\0003
(If I disable my physical adapter, then the MACAddress column goes to null, and ConfigManagerErrorCode changes to non-zero).
Unfortunately, this class is simply too slow. Any query on Win32_NetworkAdapter consistently takes 0.3 seconds on my relatively modern Windows 7 Core i7-based computer. So using this will add yet another 0.3 seconds to application startup (or worse), which I find unacceptable. This is especially because I can't think of a single valid reason why it should take so long to figure out what MAC addresses and plug-and-play device IDs are on the local computer.
Searching for other methods to get a MAC address yielded the GetAdaptersInfo and the newer GetAdaptersAddresses functions. They don't have the 0.3 second penalty that WMI imposes. These functions are the ones used by the .NET Framework's NetworkInterface class (as determined by examining .NET source code), and the "ipconfig" command line tool (as determined by using Dependency Walker).
I made a simple example in C# that lists all network adapters using the NetworkInterface class. Unfortunately, using these APIs seems to have two shortcomings:
My question is: what method can I use to get the MAC address of the local computer's physical adapters (whether it is enabled or not) in a few tens of milliseconds at most?
(I'm experienced at both C# and C++ and am fine with reading other languages, so I really don't care what language might be used in answers).
EDIT: In response to Alex K's suggestion for using return immediate and forward only, and also to provide some sample WMI code for what I am doing - here is some C# code that lists the columns of interest:
public static void NetTest() {
System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
EnumerationOptions opt = new EnumerationOptions();
// WMI flag suggestions from Alex K:
opt.ReturnImmediately = true;
opt.Rewindable = false;
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\cimv2", "select MACAddress, PNPDeviceID, ConfigManagerErrorCode from Win32_NetworkAdapter", opt);
foreach (ManagementObject obj in searcher.Get()) {
Console.WriteLine("=========================================");
foreach (PropertyData pd in obj.Properties) {
Console.WriteLine("{0} = {1}", pd.Name, pd.Value);
}
}
Console.WriteLine(sw.Elapsed.TotalSeconds);
}
I called this function 3 times and each time got about 0.36 seconds printed on the last line. So the suggested flags don't seem to have any effect: positive or negative. This is not too surprising, as the answer at How to make forward-only, read-only WMI queries in C#? seems to indicate that no change in performance will be observed unless there are a large number of records (e.g. hundreds to thousands), which is not the case with Win32_NetworkAdapter table.
EDIT 2: Multiple answers have been proposed to use SendARP from the IP helper API (this is the same API that has the GetAdaptersInfo function). What advantages does this give over GetAdaptersInfo for finding the local MAC address? I cannot think of any - on the surface, GetAdaptersInfo seems to return a more thorough set of information than SendARP does for local adapters. Now that I think about it, I think that a big part of my question centers over the concept of enumeration: what adapters exist on the computer in the first place? SendARP does not perform enumeration: it assumes you already know the IP address of the adapter you want a MAC for. I need to figure out what adapters exist on the system. Some issues this raises:
These issues don't seem to be addressed by SendARP, and are the major reason I asked this question (otherwise I'd be using GetAdaptersInfo and moving on with things...).
I wound up cutting WMI completely out of the equation and made a significant improvement while still getting the information I wanted. As noted with WMI, it was taking > 0.30 seconds to get results. With my version, I can get the same information in about 0.01 seconds.
I used the setup API, configuration manager API, and then made OID requests directly on the NDIS network driver to get the MAC address. The setup API seems obnoxiously slow, especially when getting things like property values. It's imperative to keep setup API calls at a minimum. (You can actually see just how bad it is by looking at how long it takes to load the "Details" tab of a device in Device manager).
A guess on why WMI was so slow: I noted that WMI's Win32_NetworkAdapter always took the same amount of time no matter which subset of properties I queried. Seems like the WMI Win32_NetworkAdapter class programmers were lazy and didn't optimize their class to only gather requested information like other WMI classes do. They probably gather all information, whether requested or not. They probably significantly rely on Setup API to do this, and the excessive calls to the slow Setup API to get unwanted information is what makes it so slow.
High level overview of what I did:
The result is a robust indication of MAC addresses on the PC that should be immune to "fake" network cards made by VMware, VirtualBox, largely immune to network cards that are temporarily disabled, and immune to transient network cards attached via USB, ExpressCard, PC Card, or any future removable interface.
EDIT: IOCTL_NDIS_QUERY_GLOBAL_STATS isn't supported by all network cards. The vast majority work, but some Intel cards do not. See How to reliably and quickly get the MAC address of a network card given its device instance ID
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