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

发布于 2024-08-16 05:22:23 字数 3792 浏览 7 评论 0原文

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

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

替代文字

替代文本

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

草莓酥 2024-08-23 05:22:23

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

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

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

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

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.

追星践月 2024-08-23 05:22:23

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

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

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

快速浏览一下如何在 .NET 中执行此操作:使用
管理强类型类生成器(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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文