The best way to always detect a removable device
Asked Answered
S

2

6

In my previous question "How to find the unique serial number of a flash device?" I ended up asking for a way to get the drive letter. That problem is solved.

However, my initial question has not been answered. I wanted to be able to tell removable devices (USB drives, SD cards, (external HDDs?), etc.) apart and always be able to recognise them again when they are reconnected. This should also be possible on any other computer. Luckily, I don't care about the drives being formatted (if/when they are, they are treated as new drives in my program), so can I use partition and volume IDs as a part of my recognition? I am asking this as PNPDeviceID is NOT unique. I found that it depends on the hardware reading it, see below pictures:

alt text

alt text

So, what I am searching for is a way to detect and recognise any removable device on any computer using the following: Win32_DiskDrive, Win32_DiskPartition, Win32_LogicalDisk. I am thanking RRUZ for the original code:

program GetWMI_USBConnectedInfo;

{$APPTYPE CONSOLE}

uses
  Windows,
  Classes,
  ActiveX,
  Variants,
  SysUtils,
  WbemScripting_TLB in '..\..\Documents\RAD Studio\5.0\Imports\WbemScripting_TLB.pas';

procedure  GetUSBDiskDriveInfo;
var
  WMIServices  : ISWbemServices;
  Root,a,b     : ISWbemObjectSet;
  Item,Item2   : Variant;
  i,ii,iii,iiii: Integer;
  start,stop,freq:Int64;
begin
  QueryPerformanceFrequency(freq);
  QueryPerformanceCounter(start);

  WMIServices := CoSWbemLocator.Create.ConnectServer('.', 'root\cimv2','', '', '', '', 0, nil);
  Root := WMIServices.ExecQuery('Select * From Win32_DiskDrive','WQL', 0, nil);
  for i := 0 to Root.Count - 1 do
  begin
    Item := Root.ItemIndex(i);
    for ii := VarArrayLowBound(Item.Capabilities, 1) to VarArrayHighBound(Item.Capabilities, 1) do if (Item.Capabilities[ii] = 7) then begin
      Writeln('Caption      '+VarToStr(Item.Caption));
      Writeln('Name         '+VarToStr(Item.Name));
      Writeln('DeviceID     '+VarToStr(Item.DeviceID));
      Writeln('Partitions   '+VarToStr(Item.Partitions));
      Writeln('PNPDeviceID  '+VarToStr(Item.PNPDeviceID));
      Writeln('SerialNumber '+VarToStr(Item.SerialNumber));
      Writeln('Signature    '+VarToStr(Item.Signature));

      a := WMIServices.ExecQuery('ASSOCIATORS OF {Win32_DiskDrive.DeviceID=''' + VarToStr(Item.DeviceID) + '''} WHERE AssocClass = Win32_DiskDriveToDiskPartition','WQL', 0, nil);
      for iiii := 0 to a.Count - 1 do begin
        b := WMIServices.ExecQuery('ASSOCIATORS OF {Win32_DiskPartition.DeviceID=''' + VarToStr(Variant(a.ItemIndex(iiii)).DeviceID) + '''} WHERE AssocClass = Win32_LogicalDiskToPartition','WQL', 0, nil);
        for iii := 0 to b.Count - 1 do begin
          Item2 := b.ItemIndex(iii);
          Writeln('Drive = ' + Item2.Caption);
        end;
      end;
      Writeln;
      Writeln;
    end;
  end;
  QueryPerformanceCounter(stop);
  if (freq > 0) then
    Writeln('Time took: ' + FloatToStr((stop-start) / freq))
  else
    Writeln('Unable to measure time!');
end;

begin
  try
    CoInitialize(nil);
    GetUSBDiskDriveInfo;
    Readln;
    CoUninitialize;
  except
    on E:Exception do
    Begin
        CoUninitialize;
        Writeln(E.Classname, ': ', E.Message);
        Readln;
    End;
  end;
end.

EDIT
I should add that the code detecting the drives when they are inserted is already working, though it only gives me the drive letter. I use that drive letter to get all the other info from the WMI.

Final edit
I have read that developers can safely use partition/volume ID for recognition. Can I count on that?

Solution:
So, since reading the "unique" id's is not a viable solution, there are two ways to get around this:

  1. Save a hidden file on the drive with a unique id that the program can recognize (compare to a local database).
  2. Save everything related to the drive on the drive in form of a hidden settings file. I took this approach since the program itself doesn't have any settings. All settings are per partition. This also makes the settings/program portable.
Sands answered 22/12, 2009 at 20:1 Comment(0)
R
2

You should be able to use the volume id paired with total disk size and volume name to determine if a disk is the same or not, although volume id by itself should be sufficient.

The only problem might be mass produced media. In some instances the volume id, disk size and volume name would match for all copies.

EDIT I'm not sure if you can absolutely get around it for all devices generically. Hardware from each vendor is different, and the specifications are up for interpretation, which is why serial number for some devices is null, for some its not. Your only hope would be to either supply the hardware yourself or to require specific hardware which behaves in a predictable manner.

If you can write to the device, and it would be acceptable to the user, you could create a read only system hidden file containing a unique identifier (such as a guid) and use that file for comparison. This would only stop average users who run windows with the defaults (hide system files, and don't have show hidden files checked) from copying the file, and including the volume id, disk size and volume name also in your check would insist that it is only allowed to a mirrored device. It might not get all instances, but it might get enough.

Ransdell answered 22/12, 2009 at 21:43 Comment(2)
Here's the million dollar question: How (any hack allowed) would I be able to get around that problem? I could tell the user to rename the device and use that for recognition.Sands
Excellent. Saving a GUID on the removable device and comparing that to a local database (read: INI file) where GUID and volume/partition ID is saved should ensure that the device will be recognised and even if the file is copied the volume/partition ID mismatch should stop the program from falsely identifying the device. Here's your checkmark!Sands
R
2

USB devices are required to have a unique ID. I have used that succesfully in the past from a .NET app, and that should work equally well in a Delphi app.

You should be able to import the WMI COM objects in Delphi using the Type Library Editor, that way you can use early binding in stead of Variants.

If you need an actual sample, I'd need to lookup the C# code. Drop me a private message or email if you need it.

Just had a quick look at how you do this in .NET: you use the Management Strongly Typed Class Generator (Mgmtclassgen.exe) which is not available in the native world.

I'm not so sure about non-USB devices. You'd expect the PNP ID's to be the same for devices, but you state they are not, however I don't see the same PNP ID's with different devices in your example (if course I might overlook something obvious here ).

--jeroen

USB devices have a unique ID.

Found a few parts of the C# code that lists all USB devices:

    public static List<DiskDrive> GetUsbDiskDrives()
    {
        DiskDrive.DiskDriveCollection diskDrives = DiskDrive.GetInstances("InterfaceType = 'USB'");
        return DiskDriveList(diskDrives);
    }

    public static List<DiskDrive> DiskDriveList(DiskDrive.DiskDriveCollection diskDrives)
    {
        List<DiskDrive> result = new List<DiskDrive>();
        foreach (DiskDrive diskDrive in diskDrives)
        {
            result.Add(diskDrive);
        }
        return result;
    }

    public static string Serial(DiskDrive diskDrive)
    {
        // pick the last portion of diskDrive.PNPDeviceID:
        string[] splitted = diskDrive.PNPDeviceID.Split('\\'); // note this becomes one backslash
        string result = splitted[splitted.Length - 1];
        return result;
    }

Above pieces use the .NET System.Collections.Generic namespace so you can have a generic List.

DiskDrive is in the Win32.WMI namespace in the C# file generated by this command:

MgmtClassGen.exe Win32_DiskDrive /oWin32.WMI
Rotow answered 22/12, 2009 at 22:2 Comment(8)
What would the name of the actual unique ID be, if that is obtainable with WMI?Sands
I think it is PNPDeviceID, but I need to get the C# code from TFS to verify.Rotow
PNPDeviceID is good for distinguishing devices and maybe even recognise them on the same hardware on the same computer (maybe even same USB slot). But beyond that PNPDeviceID is no use, since it changes if used with another piece of hardware.Sands
It was the last portion of PNPDeviceID. For USB devices, that one is the same for the same USB on different computers.Rotow
So you can guarantee me that that part will be the same where-ever the USB drives go? But other removable devices (SD, etc.) are tougher to play with?Sands
See this text on the link below on the uniqueness: "Q: Must a USB storage device contain a unique serial number?". blogs.msdn.com/oldnewthing/archive/2004/11/10/255047.aspx We did not try other removable devices because USB devices sufficed our needs.Rotow
Well then, I guess I have to go with skamradt's idea.Sands
I took a different approach. I just saved the settings ON the device and look for them every time a device is inserted.Sands

© 2022 - 2024 — McMap. All rights reserved.