6

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

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

替代文字

替代文字

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

2 回答 2

2

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

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

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

刚刚快速了解了如何在 .NET 中执行此操作:您使用 管理强类型类生成器 (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
于 2009-12-22T22:02:02.793 回答
2

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

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

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

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

于 2009-12-22T21:43:52.477 回答