4

How can I retrieve the manufacturer serial number of an USB flash drive in Delphi ?

I have tried this:

function GetDiskVolSerialID(ADriveName: Char): Cardinal;
var
  DiskDrive: string;
  FileSystemFlags: DWORD;
  VolumeSerialNumber: DWORD;
  MaximumComponentLength: DWORD;
begin
  DiskDrive := ADriveName + ':\';
  GetVolumeInformation(PChar(DiskDrive),
                       nil,
                       0,
                       @VolumeSerialNumber,
                       MaximumComponentLength,
                       FileSystemFlags,
                       nil,
                       0);
  Result := VolumeSerialNumber;
end;

But it doesn't return correct result!

TLama
  • 75,147
  • 17
  • 214
  • 392
opc0de
  • 11,557
  • 14
  • 94
  • 187
  • Do you want the serial number assigned by windows when a hard disk is formatted or the serial number from the manufacturer? – RRUZ Nov 27 '10 at 15:57
  • The serial from the manufacturer! – opc0de Nov 27 '10 at 16:04
  • 3
    The WMI was created to facilitate the access to information system including the hardware, The WMI is the perfect tool to this kind of task, because is very straightforward to use. i don't know what is your motivation to don't use the WMI. can you explain that? – RRUZ Nov 27 '10 at 16:24
  • it's hard to implement in delphi i tryed but failed if you have some code i will be grateful ;) – opc0de Nov 27 '10 at 17:23
  • 2
    I too agree with RRUZ. Would you agree to remove the WMI constraint? if not, is there another reason? you ask for help with an artificial requirement that may make it harder just because your belief it should be easier. – PA. Nov 27 '10 at 19:04

2 Answers2

12

opc0de, according to your comments i will give you a sample using the WMI.

First, the code which you posted (using the GetVolumeInformation function) return the serial number assigned by windows when you format a disk.

The good news are which exist two wmi classes wich exposes a property called SerialNumber which store the Number allocated by the manufacturer to identify the physical media. these classes are Win32_DiskDrive and Win32_PhysicalMedia.

Now the bad news, unfortunately this classes is not associated directly with the letter (C,D,E,F...) of the logical disk, because that you must call to another wmi classes to find the link between the logical driver letter and the Physical drive.

so you must find this link previous to obtain the serial number. the sequence to find this association is like this.

Win32_DiskPartition -> Win32_LogicalDiskToPartition -> Win32_DiskDrive

this is the code to obtain the serial number of a usb using the Win32_DiskDrive class.

program GetWMI_Info;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  StrUtils,
  ActiveX,
  ComObj,
  Variants;

function VarArrayToStr(const vArray: variant): string;

    function _VarToStr(const V: variant): string;
    var
    Vt: integer;
    begin
    Vt := VarType(V);
        case Vt of
          varSmallint,
          varInteger  : Result := IntToStr(integer(V));
          varSingle,
          varDouble,
          varCurrency : Result := FloatToStr(Double(V));
          varDate     : Result := VarToStr(V);
          varOleStr   : Result := WideString(V);
          varBoolean  : Result := VarToStr(V);
          varVariant  : Result := VarToStr(Variant(V));
          varByte     : Result := char(byte(V));
          varString   : Result := String(V);
          varArray    : Result := VarArrayToStr(Variant(V));
        end;
    end;

var
i : integer;
begin
    Result := '[';
     if (VarType(vArray) and VarArray)=0 then
       Result := _VarToStr(vArray)
    else
    for i := VarArrayLowBound(vArray, 1) to VarArrayHighBound(vArray, 1) do
     if i=VarArrayLowBound(vArray, 1)  then
      Result := Result+_VarToStr(vArray[i])
     else
      Result := Result+'|'+_VarToStr(vArray[i]);

    Result:=Result+']';
end;

function VarStrNull(const V:OleVariant):string; //avoid problems with null strings
begin
  Result:='';
  if not VarIsNull(V) then
  begin
    if VarIsArray(V) then
       Result:=VarArrayToStr(V)
    else
    Result:=VarToStr(V);
  end;
end;


function GetWMIObject(const objectName: String): IDispatch; //create the Wmi instance
var
  chEaten: Integer;
  BindCtx: IBindCtx;
  Moniker: IMoniker;
begin
  OleCheck(CreateBindCtx(0, bindCtx));
  OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));
  OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
end;



function GetUsbDriveSerial(const Drive:AnsiChar):string;
var
  objWMIService  : OLEVariant;
  colDiskDrives  : OLEVariant;
  colLogicalDisks: OLEVariant;
  colPartitions  : OLEVariant;
  objDiskDrive   : OLEVariant;
  objPartition   : OLEVariant;
  objLogicalDisk : OLEVariant;
  oEnumDiskDrive : IEnumvariant;
  oEnumPartition : IEnumvariant;
  oEnumLogical   : IEnumvariant;
  iValue         : LongWord;
  DeviceID       : string;
begin;
  Result:='';
  objWMIService := GetWMIObject('winmgmts:\\localhost\root\CIMV2'); //Connect to the WMI
  //colDiskDrives := objWMIService.ExecQuery('SELECT DeviceID,SerialNumber FROM Win32_DiskDrive WHERE InterfaceType="USB"','WQL',0); 
  colDiskDrives := objWMIService.ExecQuery('SELECT * FROM Win32_DiskDrive WHERE InterfaceType="USB"','WQL',0);
  oEnumDiskDrive:= IUnknown(colDiskDrives._NewEnum) as IEnumVariant;
  while oEnumDiskDrive.Next(1, objDiskDrive, iValue) = 0 do
  begin
     DeviceID        := StringReplace(VarStrNull(objDiskDrive.DeviceID),'\','\\',[rfReplaceAll]); //Escape the `\` chars in the DeviceID value because the '\' is a reserved character in WMI.
     colPartitions   := objWMIService.ExecQuery(Format('ASSOCIATORS OF {Win32_DiskDrive.DeviceID="%s"} WHERE AssocClass = Win32_DiskDriveToDiskPartition',[DeviceID]));//link the Win32_DiskDrive class with the Win32_DiskDriveToDiskPartition class 
     oEnumPartition  := IUnknown(colPartitions._NewEnum) as IEnumVariant;
      while oEnumPartition.Next(1, objPartition, iValue) = 0 do
       begin
            colLogicalDisks := objWMIService.ExecQuery('ASSOCIATORS OF {Win32_DiskPartition.DeviceID="'+VarStrNull(objPartition.DeviceID)+'"} WHERE AssocClass = Win32_LogicalDiskToPartition'); //link the Win32_DiskPartition class with theWin32_LogicalDiskToPartition class.
            oEnumLogical  := IUnknown(colLogicalDisks._NewEnum) as IEnumVariant;
              while oEnumLogical.Next(1, objLogicalDisk, iValue) = 0 do
              if VarStrNull(objLogicalDisk.DeviceID)=(Drive+':')  then //compare the device id
              begin
                  Result:=VarStrNull(objDiskDrive.SerialNumber);
                  Exit;
              end;
       end;
  end;
end;

begin
 try
    CoInitialize(nil);
    try
      Writeln(GetUsbDriveSerial('F'));
      Readln;
    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
    begin
        Writeln(E.Classname, ':', E.Message);
        Readln;
    end;
  end;
end.

By the way some time ago i wrote an application called WMI Delphi Code Creator which can help you to generate delphi code to access the system info using the WMI.

UPDATE

Some drivers of the USB disks does not expose the manufacturer serial number on the Win32_DiskDrive.SerialNumber property, so on this cases you can extract the serial number from the PnPDeviceID property.

Check this sample code.

{$APPTYPE CONSOLE}

uses
  SysUtils,
  StrUtils,
  ActiveX,
  ComObj,
  Variants;


function VarStrNull(const V:OleVariant):string; //avoid issues with null variants
begin
  Result:='';
  if not VarIsNull(V) then
    Result:=VarToStr(V);
end;


function GetUsbDriveSerial(const Drive:AnsiChar):string;
var
 FSWbemLocator   : OleVariant;
  objWMIService  : OLEVariant;
  colDiskDrives  : OLEVariant;
  colLogicalDisks: OLEVariant;
  colPartitions  : OLEVariant;
  objDiskDrive   : OLEVariant;
  objPartition   : OLEVariant;
  objLogicalDisk : OLEVariant;
  oEnumDiskDrive : IEnumvariant;
  oEnumPartition : IEnumvariant;
  oEnumLogical   : IEnumvariant;
  iValue         : LongWord;
  DeviceID       : string;
begin;
  Result:='';
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  objWMIService := FSWbemLocator.ConnectServer('.', 'root\CIMV2', '', '');
  colDiskDrives := objWMIService.ExecQuery('SELECT * FROM Win32_DiskDrive WHERE InterfaceType="USB"','WQL',0);
  oEnumDiskDrive:= IUnknown(colDiskDrives._NewEnum) as IEnumVariant;
  while oEnumDiskDrive.Next(1, objDiskDrive, iValue) = 0 do
  begin
     DeviceID        := StringReplace(VarStrNull(objDiskDrive.DeviceID),'\','\\',[rfReplaceAll]); //Escape the `\` chars in the DeviceID value because the '\' is a reserved character in WMI.
     colPartitions   := objWMIService.ExecQuery(Format('ASSOCIATORS OF {Win32_DiskDrive.DeviceID="%s"} WHERE AssocClass = Win32_DiskDriveToDiskPartition',[DeviceID]));//link the Win32_DiskDrive class with the Win32_DiskDriveToDiskPartition class
     oEnumPartition  := IUnknown(colPartitions._NewEnum) as IEnumVariant;
      while oEnumPartition.Next(1, objPartition, iValue) = 0 do
       begin
        colLogicalDisks := objWMIService.ExecQuery('ASSOCIATORS OF {Win32_DiskPartition.DeviceID="'+VarStrNull(objPartition.DeviceID)+'"} WHERE AssocClass = Win32_LogicalDiskToPartition'); //link the Win32_DiskPartition class with theWin32_LogicalDiskToPartition class.
        oEnumLogical  := IUnknown(colLogicalDisks._NewEnum) as IEnumVariant;
          while oEnumLogical.Next(1, objLogicalDisk, iValue) = 0 do
          begin
            if  SameText(VarStrNull(objLogicalDisk.DeviceID),Drive+':')  then //compare the device id
            begin
              Result:=VarStrNull(objDiskDrive.PnPDeviceID);
              if AnsiStartsText('USBSTOR', Result) then
              begin
               iValue:=LastDelimiter('\', Result);
                Result:=Copy(Result, iValue+1, Length(Result));
              end;
              objLogicalDisk:=Unassigned;
              Exit;
            end;
            objLogicalDisk:=Unassigned;
          end;
          objPartition:=Unassigned;
       end;
       objDiskDrive:=Unassigned;
  end;
end;

begin
 try
    CoInitialize(nil);
    try
      Writeln(GetUsbDriveSerial('F'));
      Readln;
    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
    begin
        Writeln(E.Classname, ':', E.Message);
        Readln;
    end;
  end;
end.
Community
  • 1
  • 1
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • while running the code i occured the following exception : Invalid Query any ideea why ? – opc0de Nov 27 '10 at 21:28
  • 2
    ok , that is due to your WMI version change this line `colDiskDrives := objWMIService.ExecQuery('SELECT DeviceID,SerialNumber FROM Win32_DiskDrive WHERE InterfaceType="USB"','WQL',0); ` to `colDiskDrives := objWMIService.ExecQuery('SELECT * FROM Win32_DiskDrive WHERE InterfaceType="USB"','WQL',0);` – RRUZ Nov 27 '10 at 22:53
  • The code was edited to be compatible con old versions of WMI. – RRUZ Nov 27 '10 at 22:58
  • The above code does not work under Delphi 7 running under Windows 7 32-bit. The console window appears, but all one sees is a blinking underscore. – user1527613 Aug 25 '12 at 23:13
  • 3
    This is because some drivers of the USB disks does not expose the manufacturer serial number on the `Win32_DiskDrive.SerialNumber` property. – RRUZ Aug 26 '12 at 01:17
  • It works with `objDiskDrive.PNPDeviceID`, but if changed to `objDiskDrive.Manufacturer` it returns '(standard disk drives)'. I need the manufacturer name! – Delphi.Boy Nov 04 '13 at 09:00
  • Sorry for awakening old thread, but... RRUZ thanks for the code. Two questions: 1st: am I allowed to use it as part of licensing for my software? Second: How safe actually is getting this serial number based on Win32_DiskDrive for licensing, aka can it be faked in some way? Thanks! – That Marc Oct 07 '15 at 04:17
  • 1
    @JustMarc, 1.) Yes your allowed to use this sample in your code, 2) Is technically possible fake the serial number, for avoid this you can try using a combination of hardware info like processor, BIOS, RAM, etc. for an example try this link https://theroadtodelphi.wordpress.com/2010/12/02/generating-a-unique-hardware-id-using-delphi-and-the-wmi/ – RRUZ Oct 07 '15 at 17:03
  • @PRUZ this code return nothing on windows 8.1 .i need to save a unique id of flash then use flash as a hardware lock of the software.is this possible? – peiman F. Feb 24 '17 at 15:48
2

You can try the component TDiskInfo from GLib to Get the SerialNumber.
It not use WMI, but in some system (disk types) not retrieve the number.
Try it. It's free.

Regards.