Link to home
Start Free TrialLog in
Avatar of axp275
axp275Flag for United States of America

asked on

USB Flash Drive Serial Numbers

I am writing a program using Delphi 6 which I would like to run from a USB flash drive and I want to record the serial number of each flash drive when the software is copied to it for the first time.  When the program is opened I want to compare the serial number of the flash drive to the one I recorded when the flash drive software was created.  I don't want the serial number of the drive's volume when formatted.  This is the hardware serial number.  I have found a couple of methods that work but they fail when Vista/Windows 7/8 is used.
Avatar of Sinisa Vuk
Sinisa Vuk
Flag of Croatia image

Avatar of axp275

ASKER

That link will get me the volume s/n.  That changes with each format so it won't work.  Thanks.
Avatar of axp275

ASKER

None of those solutions work for me and I have seen them a very long time ago.  I have solved this puzzle.  see code below:


function GetFlashDriveSerialNumber(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;//if
                              objLogicalDisk:=Unassigned;
                              Exit;
                        end;//if
                        objLogicalDisk:=Unassigned;
                  end;//while
                  objPartition:=Unassigned;
            end;//while
            objDiskDrive:=Unassigned;
  end;//while
end;
Avatar of axp275

ASKER

I've requested that this question be closed as follows:

Accepted answer: 0 points for axp275's comment #a39203803

for the following reason:

After I posted my problem I discovered a solution that did exactly what I needed it to do and I have posted the code above.  I don't deserve any points for this because I am using code from an example I found.  I have only adjusted it to compile properly and work for my project.  Thanks to all of those who assisted.
ASKER CERTIFIED SOLUTION
Avatar of Sinisa Vuk
Sinisa Vuk
Flag of Croatia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
try the following code (translated from http://oroboro.com/usb-serial-number/).
you will need JVCL, Jcl and PerlRegEx libraries, too.
Jcl / JVCL : http://sourceforge.net/projects/jvcl/files/JVCL%203/JVCL%203.47/JVCL347CompleteJCL241-Build4571.zip/download
PerlRegEx : http://www.regular-expressions.info/delphi.html
program usbsn;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows, JvSetupApi, PerlRegEx;

type
  DEVICE_TYPE = DWORD;
  STORAGE_DEVICE_NUMBER = record
    //
    // The FILE_DEVICE_XXX type for this device.
    //
    DeviceType: DEVICE_TYPE;
    //
    // The number of this device
    //
    DeviceNumber: DWORD;
    //
    // If the device is partitionable, the partition number of the device.
    // Otherwise -1
    //
    PartitionNumber: DWORD;
  end;

const
  FILE_ANY_ACCESS                     = 0;
  METHOD_BUFFERED                     = 0;
  FILE_DEVICE_MASS_STORAGE            = $0000002D;
  IOCTL_STORAGE_BASE                  = FILE_DEVICE_MASS_STORAGE;
  IOCTL_STORAGE_GET_DEVICE_NUMBER     = (IOCTL_STORAGE_BASE shl 16) or (FILE_ANY_ACCESS shl 14) or
                                        ($0420 shl 2) or METHOD_BUFFERED;
  GUID_DEVINTERFACE_USB_DISK: TGUID = (
    D1: $53f56307;
    D2: $b6bf;
    D3: $11d0;
    D4: ($94, $f2, $00, $a0, $c9, $1e, $fb, $8b);
  );

function getDeviceNumber(deviceHandle: THandle): DWORD;
var
  sdn: STORAGE_DEVICE_NUMBER;
  dwBytesReturned: DWORD;
begin
  sdn.DeviceNumber := MAXDWORD;
  dwBytesReturned := 0;
  if not DeviceIoControl(deviceHandle, IOCTL_STORAGE_GET_DEVICE_NUMBER,
  nil, 0, @sdn, SizeOf(sdn), dwBytesReturned, nil) then
   // handle error - like a bad handle.
    Result := MAXDWORD
  else
    Result := sdn.DeviceNumber;
end;

function getDeviceSerialNumber(vol: Char): string;
var
  devicePath: string;
  deviceHandle: THandle;
  volumeDeviceNumber: DWORD;
  hDev: HDEVINFO;
  Buf: array[0..1023] of Byte;
  pspdidd: PSPDeviceInterfaceDetailData;
  spdid: SP_DEVICE_INTERFACE_DATA;
  spdd: SP_DEVINFO_DATA;
  dwIndex, dwSize: DWORD;
  res: LongBool;
  hDrive: THandle;
  usbDeviceNumber: DWORD;
  re: TPerlRegEx;
begin
  // get the device handle
  devicePath := '\\.\' + vol + ':';

  deviceHandle := CreateFile(PChar(devicePath), 0,
    FILE_SHARE_READ or FILE_SHARE_WRITE, nil,
    OPEN_EXISTING, 0, 0);
  if deviceHandle = INVALID_HANDLE_VALUE then
    Exit;

  // to get the device number
  volumeDeviceNumber := getDeviceNumber(deviceHandle);
  CloseHandle(deviceHandle);

  // Get device interface info set handle
  // for all devices attached to the system
  hDev := SetupDiGetClassDevs(
    @GUID_DEVINTERFACE_USB_DISK, nil, 0,
    DIGCF_PRESENT or DIGCF_DEVICEINTERFACE);

  if hDev = HDEVINFO(INVALID_HANDLE_VALUE) then
    Exit;

  // Get a context structure for the device interface
  // of a device information set.
  Cardinal(pspdidd) := Cardinal(@Buf);

  spdid.cbSize := SizeOf(spdid);

  dwIndex := 0;
  while True do begin
    if not SetupDiEnumDeviceInterfaces(hDev, nil,
    GUID_DEVINTERFACE_USB_DISK, dwIndex, spdid) then
       Break;

    dwSize := 0;
    SetupDiGetDeviceInterfaceDetail(hDev, @spdid, nil,
      0, dwSize, nil);

    if (dwSize <> 0) and (dwSize <= SizeOf(Buf)) then begin
      pspdidd^.cbSize := SizeOf(pspdidd^); // 5 Bytes!

      ZeroMemory(@spdd, SizeOf(spdd));
      spdd.cbSize := SizeOf(spdd);

      res := SetupDiGetDeviceInterfaceDetail(
        hDev, @spdid, pspdidd, dwSize, dwSize, @spdd);
      if res then begin
        hDrive := CreateFile(pspdidd^.DevicePath, 0,
          FILE_SHARE_READ or FILE_SHARE_WRITE,
          nil, OPEN_EXISTING, 0, 0);
        if hDrive <> INVALID_HANDLE_VALUE then begin
          usbDeviceNumber := getDeviceNumber(hDrive);

          if usbDeviceNumber = volumeDeviceNumber then begin
            re := TPerlRegEx.Create;
            re.RegEx := '&rev_[^#]+#([^&#]+)';
            re.Subject := PChar(@pspdidd^.DevicePath);
            if re.Match then begin
              Result := re.Groups[1];
              Exit;
            end;
          end;
        end;
        CloseHandle(hDrive);
      end;
    end;
    Inc(dwIndex);
  end;

  SetupDiDestroyDeviceInfoList(hDev);
end;

begin
  try
    LoadSetupApi;
    Writeln(getDeviceSerialNumber('N'));
    UnloadSetupApi;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Open in new window

Avatar of axp275

ASKER

This user did in fact send a link with the same code I used even though I discovered this on my own from a different link so they should get the points for the solution.
Avatar of johnnyex
johnnyex

sorry again, confused so which solution is best?
here we can see:
https://www.experts-exchange.com/questions/28140731/USB-Flash-Drive-Serial-Numbers.html?anchorAnswerId=39203803#a39203803

There we can see two methods:
http://stackoverflow.com/questions/4292395/how-to-get-manufacturer-serial-number-of-an-usb-flash-drive


Where author says: "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."


So which one is the best method which will work on all drives or maybe use two hybrid if one fail?

Would be good to have one solid solution which will just work is this from first link I posted or second method from stackoverflow link?