7

I would like to use a unique identifier to determine whether my application moved to a different computer. The MAC address seems to be suitable for this purpose. The code I use is this:

Procedure TForm4.GetMacAddress;
var item: TListItem;
    objWMIService : OLEVariant;
    colItems      : OLEVariant;
    colItem       : OLEVariant;
    oEnum         : IEnumvariant;
    iValue        : LongWord;
    wmiHost, root, wmiClass: string;
    i: Int32;

  function GetWMIObject(const objectName: String): IDispatch;
  var
    chEaten: Integer;
    BindCtx: IBindCtx;//for access to a bind context
    Moniker: IMoniker;//Enables you to use a moniker object
  begin
    OleCheck(CreateBindCtx(0, bindCtx));
    OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));//Converts a string into a moniker that identifies the object named by the string
    OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));//Binds to the specified object
  end;

begin
   wmiHost       := '.';
   root          := 'root\CIMV2';
   wmiClass      := 'Win32_NetworkAdapterConfiguration';
   objWMIService := GetWMIObject(Format('winmgmts:\\%s\%s',[wmiHost,root]));
   colItems      := objWMIService.ExecQuery(Format('SELECT * FROM %s',[wmiClass]),'WQL',0);
   oEnum         := IUnknown(colItems._NewEnum) as IEnumVariant;
   i := 0;
   while oEnum.Next(1, colItem, iValue) = 0 do
   begin
      Item := View.Items.Add;
      item.Caption := Copy (colItem.Caption, 2, 8);

      Item.SubItems.Add (colItem.Description);
      Item.SubItems.Add (colItem.ServiceName);
      Item.SubItems.Add (VarToStrNil (colItem.MACAddress));
      if (VarToStrNil(colItem.MACAddress) <> '')
         then Item.SubItems.Add ('yes')
         else Item.SubItems.Add ('no');
      if colItem.IPEnabled
         then Item.SubItems.Add ('yes')
         else Item.SubItems.Add ('no');
     Item.SubItems.Add (VarToStrNil (colItem.SettingID));
     Item.SubItems.Add (IntToStr (colItem.InterfaceIndex));
   end; // if
end; // GetMacAddress //

My machine has one network port, but this code finds 18 network related ports/things/whatever. Among them there are four MAC adresses. I presume that a network port should be IP enabled so that leaves two left (labeled MAC in the image). Is it correct to assume that of the ports thus filtered, the one with the lowest index is the hardware port?

enter image description here

Edit in the snapshot above the Realtek adapter is the only physical adapter in the machine. The other adapter is the VirtualBox virtual adapter. The answer of TLama identifies these two adapters, but is there a way to find the address of the only Physical (Realtek) adapter?

Update 1 EJP pointed out that the MAC address can be changed. This somewhat undermines my purpose, but as I am looking for a solution that fits most situations I decided to live with it.

TLama and TOndrej pointed to several solutions. Both end up with a situation that a physical adapter could not be found without any doubt.

Update 2 TLama's excellent reading list shows that there is probably not a certain way to determine the physical adapter. The article mentioned in the first bullet shows how to shrink the amount of adapters based on some simple assumptions. The article in the third bullet shows how to select the adapter connected to the PCI bus, which is in fact exactly what I wanted to know. There are some weird exceptions mentioned in the article but I think this will provide an answer in most cases.

Thank you all for your contributions!

Arnold
  • 4,578
  • 6
  • 52
  • 91

2 Answers2

8

Use the Win32_NetworkAdapter class instead. It has the PhysicalAdapter member. The following example should list you the MAC addresses of physical adapters:

program Program1;

{$APPTYPE CONSOLE}

uses
  SysUtils, ActiveX, ComObj, Variants;

procedure GetWin32_NetworkAdapterInfo;
const
  WbemUser = '';
  WbemPassword = '';
  WbemComputer = 'localhost';
  wbemFlagForwardOnly = $00000020;
var
  ElementCount: LongWord;
  FWMIService: OleVariant;
  FWbemObject: OleVariant;
  EnumVariant: IEnumVARIANT;
  FSWbemLocator: OleVariant;
  FWbemObjectSet: OleVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  FWbemObjectSet := FWMIService.ExecQuery('SELECT * FROM Win32_NetworkAdapter WHERE PhysicalAdapter = 1', 'WQL', wbemFlagForwardOnly);
  EnumVariant := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while EnumVariant.Next(1, FWbemObject, ElementCount) = 0 do
  begin
    Writeln(Format('MACAddress %s', [VarToStr(FWbemObject.MACAddress)]));
    FWbemObject := Unassigned;
  end;
end;

begin
  try
    CoInitialize(nil);
    try
      GetWin32_NetworkAdapterInfo;
    finally
      CoUninitialize;
    end;
  except
    on E:EOleException do
      Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
    on E:Exception do
      Writeln(E.Classname, ':', E.Message);
  end;
  Writeln('Press Enter to exit');
  Readln;
end.

Based on the code generated by the WMI Delphi Code Creator.

Update:

You are actually trying to filter out the adapters which belongs to virtual machines, what is not that easy since they are simulated like to be as physical ones (you can even see them in Device Manager as the physical adapters), so you cannot distinguish them e.g.:

  • by the DHCPEnabled member of the Win32_NetworkAdapter class, because even virtual machines might be configured so they get IP address from a DHCP server
  • by the AdapterTypeId member of the Win32_NetworkAdapter class, since there is no special type for virtual adapters
  • by the PhysicalAdapter member of the Win32_NetworkAdapter class, because they are being simulated to be physical

Additional reading:

Community
  • 1
  • 1
TLama
  • 75,147
  • 17
  • 214
  • 392
  • This yields exactly the two MAC addresses with IPEnabled in my question. I wonder how I can distinguish between the real network adapter (Realtek) and the virtual network adapter from VirtualBox. My question was not clear at that point. I'll add to it. – Arnold May 15 '12 at 09:41
  • Basically I would say this might be distinguished e.g. in `Win32_NetworkAdapterConfiguration` by the `DHCPEnabled` member, but even those virtual adapters might be configured to get the IP address from DHCP server. There is also `PNPDeviceID` which starts with the `PCI\...` but it's still not so significant. In [`this`](http://www.codeproject.com/Articles/18135/Getting-the-Network-Adaptor-MAC-Address-with-WMI) example they used `AdapterTypeId`, however the virtual adapters has no special type for it, so it's also not a solution. – TLama May 15 '12 at 10:14
  • 2
    Seems I was thinking the [`similar way`](http://weblogs.sqlteam.com/mladenp/archive/2010/11/04/find-only-physical-network-adapters-with-wmi-win32_networkadapter-class.aspx). I'm afraid those adapters are by Windows handled like a real physical network adapters, so without this tricky way it's impossible to distinguish them from a hardware ones. – TLama May 15 '12 at 10:40
  • 1
    I did experiment with the IP Helper library that TOndrej pointed at. The AdapterInfo struct has too few info as well. It is interesting code though, if you don't want to depend on WMI. – Arnold May 15 '12 at 13:09
  • 1
    I was hoping more for the `PNPDeviceID` where the root would be `PCI\...` for instance, but I don't know if this would fit for USB adapters (if there are some), but it's still not so smooth solution. The virtual adapters are just like the real hardware ones, so it's quite difficult to distinguish them. I was looking for similar topics about this, but it seems no one solved this in a clean way. – TLama May 15 '12 at 13:20
  • Wew, thanks for the additional reading! However, they all shatter at my not being able to perform a query with a WHERE clause. 'SELECT * FROM Win32_NetworkAdapterConfiguration WHERE Manufacturer!=''Microsoft''' returns 'invalid query' (all single quotes, not so visible in comment I noticed, tried double quotes, same result). Any idea what's wrong? – Arnold May 16 '12 at 06:31
  • 1
    The `Manufacturer` is in the [`Win32_NetworkAdapter`](http://msdn.microsoft.com/en-us/library/windows/desktop/aa394216%28v=vs.85%29.aspx) class, not `Win32_NetworkAdapterConfiguration`, so it should be `SELECT * FROM Win32_NetworkAdapter WHERE Manufacturer != "Microsoft"`. Anyway, the best you can do is to download the latest RRUZ's [`WMI Delphi Code Creator`](http://theroadtodelphi.wordpress.com/wmi-delphi-code-creator/), it includes also a query editor, where you can build the queries as well as generate a complete Delphi code. – TLama May 16 '12 at 07:34
  • 1
    Thanks for the clarification and for the pointer to RRUZ. I use the PNPDeviceID now, if that does not yield anything it is probably a virtual machine and I take the first vailable. To test whether it works in all circumstances I should really test 100's of machines. Anyhow, this answers my question, thank you very much for your help and explanation! – Arnold May 16 '12 at 08:20
4

You could also use GetAdaptersAddresses API from the IP Helper library. For a Delphi translation, Magenta Systems IP Helper Component looks good at first glance.

Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
  • Thanks for the pointer. Interesting code, but as far as I can see resulting in about the same kind od output as of the 'Win32_NetworkAdapterConfiguration' WMI class, in the end resulting in the same output: two ports that act as physical adapter. – Arnold May 15 '12 at 10:04