【问题标题】:The best way to always detect a removable device始终检测可移动设备的最佳方法
【发布时间】:2010-12-29 06:13:06
【问题描述】:

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

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

所以,我正在寻找一种使用以下方法检测和识别任何计算机上的任何可移动设备的方法:Win32_DiskDrive、Win32_DiskPartition、Win32_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. 以隐藏设置文件的形式保存与驱动器相关的所有内容在驱动器上。我采用这种方法是因为程序本身没有任何设置。所有设置都是针对每个分区的。这也使设置/程序可移植。

【问题讨论】:

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


    【解决方案1】:

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

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

    编辑我不确定您是否可以在所有设备上完全绕过它。每个供应商的硬件都不同,规格有待解释,这就是为什么有些设备的序列号为空,而有些则不是。您唯一的希望是要么自己提供硬件,要么要求以可预测方式运行的特定硬件。

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

    【讨论】:

    • 这是一个百万美元的问题:我如何(允许任何黑客攻击)解决这个问题?我可以告诉用户重命名设备并将其用于识别。
    • 优秀。在可移动设备上保存 GUID 并将其与保存 GUID 和卷/分区 ID 的本地数据库(读取:INI 文件)进行比较,应确保设备将被识别,即使文件被复制,卷/分区 ID 不匹配应该阻止程序错误地识别设备。这是你的复选标记!
    【解决方案2】:

    USB 设备必须具有唯一 ID。 我过去曾在 .NET 应用程序中成功使用过它,并且在 Delphi 应用程序中应该同样有效。

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

    如果您需要实际示例,我需要查找 C# 代码。如果需要,请给我发私信或电子邮件。

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

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

    --杰罗恩

    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
    

    【讨论】:

    • 如果可以通过 WMI 获得,实际唯一 ID 的名称是什么?
    • 我认为是PNPDeviceID,但我需要从TFS获取C#代码来验证。
    • PNPDeviceID 非常适合区分设备,甚至可以在同一台计算机上的相同硬件上识别它们(甚至可能是相同的 USB 插槽)。但除此之外,PNPDeviceID 没有任何用处,因为如果与另一块硬件一起使用,它会发生变化。
    • 这是 PNPDeviceID 的最后一部分。对于 USB 设备,对于不同计算机上的同一个 USB,该设备是相同的。
    • 我采取了不同的方法。我只是将设置保存在设备上,并在每次插入设备时查找它们。
    猜你喜欢
    • 2019-01-26
    • 2014-12-26
    • 2016-07-16
    • 1970-01-01
    • 2011-05-06
    • 2012-08-31
    • 2012-10-23
    相关资源
    最近更新 更多