始终检测可移动设备的最佳方法

时间:2009-12-22 20:01:46

标签: delphi wmi delphi-2007 removable-drive removable

在我之前的问题“如何找到闪存设备的唯一序列号?”我最终要求找到驱动器号的方法。那个问题就解决了。

然而,我的初步问题尚未得到解答。我希望能够将可移动设备(USB驱动器,SD卡,(外部硬盘驱动器?)等)分开,并且在重新连接时始终能够再次识别它们。这也应该可以在任何其他计算机上使用。幸运的是,我不关心正在格式化的驱动器(如果/何时,它们在我的程序中被视为新驱动器),那么我可以使用分区和卷ID作为我识别的一部分吗?我问这是因为PNPDeviceID是 NOT 唯一。我发现它取决于硬件读取它,见下图:

alt text

alt text

因此,我正在搜索的是一种使用以下方法检测和识别任何计算机上任何可移动设备的方法:Win32_DiskDriveWin32_DiskPartitionWin32_LogicalDisk。我感谢RRUZ原始代码:

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.

修改
我应该补充一点,在插入驱动器时检测驱动器的代码已经可以工作了,尽管它只给了我一个驱动器号。我使用该驱动器号从WMI获取所有其他信息。

最终修改
我已经读过开发人员可以安全地使用分区/卷ID进行识别。我可以指望吗?

解决方案:
因此,由于阅读“唯一”ID不是一个可行的解决方案,有两种方法可以解决这个问题:

  1. 使用程序可识别的唯一ID保存驱动器上的隐藏文件(与本地数据库进行比较)。
  2. 以隐藏设置文件的形式保存与驱动器相关的所有内容。我采用这种方法,因为程序本身没有任何设置。所有设置均为每个分区。这也使设置/程序可移植。

2 个答案:

答案 0 :(得分:2)

您应该能够使用与磁盘总大小和卷名配对的卷ID来确定磁盘是否相同,尽管卷ID本身就足够了。

唯一的问题可能是大规模生产的媒体。在某些情况下,卷ID,磁盘大小和卷名称将匹配所有副本。

编辑我不确定你是否可以绝对解决所有设备问题。来自每个供应商的硬件是不同的,并且规范是用于解释的,这就是为什么某些设备的序列号为空的原因,而有些则不是。您唯一的希望是自己提供硬件或者需要以可预测的方式运行的特定硬件。

如果您可以写入设备,并且用户可以接受,则可以创建包含唯一标识符(例如guid)的只读系统隐藏文件,并使用该文件进行比较。这样只会阻止使用默认设置运行Windows的普通用户(隐藏系统文件,并且没有选中显示隐藏文件)复制文件,并且在您的检查中也包括卷ID,磁盘大小和卷名称将坚持它只允许镜像设备。它可能无法获得所有实例,但它可能已经足够了。

答案 1 :(得分:2)

USB设备需要具有唯一ID。 我以前从.NET应用程序中成功地使用了它,这应该在Delphi应用程序中同样有效。

您应该能够使用类型库编辑器在Delphi中导入WMI COM对象,这样您就可以使用早期绑定而不是变体。

如果你需要一个实际的样本,我需要查找C#代码。如果需要,请给我发私信或发送电子邮件。

快速浏览一下如何在.NET中执行此操作:使用原生世界中不可用的 Management Strongly Typed Class Generator (Mgmtclassgen.exe)

我对非USB设备不太确定。您希望PNP ID对于设备是相同的,但是您声明它们不是,但是我没有在您的示例中看到与不同设备相同的PNP ID(如果当然我可能会忽略这里明显的东西)。

- 的Jeroen

USB设备具有唯一ID。

找到列出所有USB设备的C#代码的一部分:

    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;
    }

以上部分使用.NET System.Collections.Generic命名空间,因此您可以拥有通用列表。

DiskDrive位于此命令生成的C#文件的Win32.WMI命名空间中:

MgmtClassGen.exe Win32_DiskDrive /oWin32.WMI