从VID / PID查找USB驱动器字母(适用于XP及以上版本)

19

因此我认为我会在这里包含最终答案,这样您就不必理解本篇文章了。非常感谢Simon Mourier花时间解决此问题。

我的工作代码

        try
        {
            //Get a list of available devices attached to the USB hub
            List<string> disks = new List<string>();
            var usbDevices = GetUSBDevices();

            //Enumerate the USB devices to see if any have specific VID/PID
            foreach (var usbDevice in usbDevices)
            {
                if (usbDevice.DeviceID.Contains(USB_PID) && usbDevice.DeviceID.Contains(USB_VID))
                {
                    foreach (string name in usbDevice.GetDiskNames())
                    {
                        //Open dialog to show file names
                        textbox1.Text = name.ToString();
                    }
                }                   
            }

只需使用我原来问题中的GetUSBDevices方法,然后包括Simon Mourier回答中展示的两个类,就可以把它做好!


原始问题:

我知道这个问题之前已经被问过了(见这里),但是它们没有一个确认的答案,我尝试了所有建议的答案。不幸的是,那些线程早已死亡,我希望有人能在这里给出一个更好的答案。

到目前为止,我有两个“起点”,我将分别展示它们。


选项 1:(获取 VID/PID 但没有驱动器号)

我有一个嵌入式设备,通过应用程序连接到它。我有代码成功扫描任何 USB 设备并检查VID/PID。我成功地检测到我的设备,但我不知道如何获取驱动器号。可以有人帮我吗?我觉得我可以在class里添加另一行,但当我通过Device Manager浏览时,找不到任何描述驱动器号的属性。

谢谢!

我将在下面包含到目前为止的代码。

private void tsDownload_Click(object sender, EventArgs e)
    {
        var usbDevices = GetUSBDevices();

        foreach (var usbDevice in usbDevices)
        {
            if (usbDevice.DeviceID.Contains(USB_PID) && usbDevice.DeviceID.Contains(USB_VID))
            {                    
                //Find drive letter here
            }
        }
    }

这些函数都在哪里:

 static List<USBDeviceInfo> GetUSBDevices()
    {
      List<USBDeviceInfo> devices = new List<USBDeviceInfo>();

      ManagementObjectCollection collection;
      using (var searcher = new ManagementObjectSearcher(@"Select * From Win32_USBHub"))
        collection = searcher.Get();      

      foreach (var device in collection)
      {
        devices.Add(new USBDeviceInfo(
        (string)device.GetPropertyValue("DeviceID"),
        (string)device.GetPropertyValue("PNPDeviceID"),
        (string)device.GetPropertyValue("Description")            
        ));
      }

      collection.Dispose();
      return devices;
    }      

并且这个类是:

class USBDeviceInfo
{
    public USBDeviceInfo(string deviceID, string pnpDeviceID, string description)
    {
      this.DeviceID = deviceID;
      this.PnpDeviceID = pnpDeviceID;
      this.Description = description;
    }
    public string DeviceID { get; private set; }
    public string PnpDeviceID { get; private set; }
    public string Description { get; private set; }
}

选项2:(获取驱动器的字母但不获取VID / PID)
foreach (ManagementObject drive in new ManagementObjectSearcher("select * from Win32_DiskDrive where InterfaceType='USB'").Get())
            {
                foreach(ManagementObject partition in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + drive["DeviceID"] + "'} WHERE AssocClass = Win32_DiskDriveToDiskPartition").Get())
                {
                    foreach (ManagementObject disk in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" + partition["DeviceID"] + "'} WHERE AssocClass = Win32_LogicalDiskToPartition").Get())
                    {
                        textBox1.Text = disk["Name"].ToString();

                    }
                }
            }

我猜测 VID / PID 应该在 disk 对象的某个属性中,但我找不到是哪一个。


1
+1 针对研究工作的努力。希望你得到你正在寻找的答案。我也很好奇。 - Geeky Guy
我认为这种困惑/难度来自于并非每个设备都有驱动器号,即使有,一个设备也可以有多个驱动器号。这只是我的初学者理解... - Gray
我已经连接了两个USB智能卡读卡器,但当我运行选项2示例时,在“ManagementObjectSearcher”中没有找到任何USB设备。 - Jitendra
3个回答

18

我可能错了,但似乎WMI不知道Windows设备安装API中存在的父子关系。

因此,我创建了一个小的Device实用程序类,可以从本机Setup API添加这个缺失的链接。以下是您在原始USBDeviceInfo类中使用它的方式:

class USBDeviceInfo
{
    public USBDeviceInfo(string deviceID, string pnpDeviceID, string description)
    {
        this.DeviceID = deviceID;
        this.PnpDeviceID = pnpDeviceID;
        this.Description = description;
    }

    public string DeviceID { get; private set; }
    public string PnpDeviceID { get; private set; }
    public string Description { get; private set; }

    public IEnumerable<string> GetDiskNames()
    {
        using (Device device = Device.Get(PnpDeviceID))
        {
            // get children devices
            foreach (string childDeviceId in device.ChildrenPnpDeviceIds)
            {
                // get the drive object that correspond to this id (escape the id)
                foreach (ManagementObject drive in new ManagementObjectSearcher("SELECT DeviceID FROM Win32_DiskDrive WHERE PNPDeviceID='" + childDeviceId.Replace(@"\", @"\\") + "'").Get())
                {
                    // associate physical disks with partitions
                    foreach (ManagementObject partition in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + drive["DeviceID"] + "'} WHERE AssocClass=Win32_DiskDriveToDiskPartition").Get())
                    {
                        // associate partitions with logical disks (drive letter volumes)
                        foreach (ManagementObject disk in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" + partition["DeviceID"] + "'} WHERE AssocClass=Win32_LogicalDiskToPartition").Get())
                        {
                            yield return (string)disk["DeviceID"];
                        }
                    }
                }
            }
        }
    }
}

这里是新的设备类:

public sealed class Device : IDisposable
{
    private IntPtr _hDevInfo;
    private SP_DEVINFO_DATA _data;

    private Device(IntPtr hDevInfo, SP_DEVINFO_DATA data)
    {
        _hDevInfo = hDevInfo;
        _data = data;
    }

    public static Device Get(string pnpDeviceId)
    {
        if (pnpDeviceId == null)
            throw new ArgumentNullException("pnpDeviceId");

        IntPtr hDevInfo = SetupDiGetClassDevs(IntPtr.Zero, pnpDeviceId, IntPtr.Zero, DIGCF.DIGCF_ALLCLASSES | DIGCF.DIGCF_DEVICEINTERFACE);
        if (hDevInfo == (IntPtr)INVALID_HANDLE_VALUE)
            throw new Win32Exception(Marshal.GetLastWin32Error());

        SP_DEVINFO_DATA data = new SP_DEVINFO_DATA();
        data.cbSize = Marshal.SizeOf(data);
        if (!SetupDiEnumDeviceInfo(hDevInfo, 0, ref data))
        {
            int err = Marshal.GetLastWin32Error();
            if (err == ERROR_NO_MORE_ITEMS)
                return null;

            throw new Win32Exception(err);
        }

        return new Device(hDevInfo, data) {PnpDeviceId = pnpDeviceId};
    }

    public void Dispose()
    {
        if (_hDevInfo != IntPtr.Zero)
        {
            SetupDiDestroyDeviceInfoList(_hDevInfo);
            _hDevInfo = IntPtr.Zero;
        }
    }

    public string PnpDeviceId { get; private set; }

    public string ParentPnpDeviceId
    {
        get
        {
            if (IsVistaOrHiger)
                return GetStringProperty(DEVPROPKEY.DEVPKEY_Device_Parent);

            uint parent;
            int cr = CM_Get_Parent(out parent, _data.DevInst, 0);
            if (cr != 0)
                throw new Exception("CM Error:" + cr);

            return GetDeviceId(parent);
        }
    }

    private static string GetDeviceId(uint inst)
    {
        IntPtr buffer = Marshal.AllocHGlobal(MAX_DEVICE_ID_LEN + 1);
        int cr = CM_Get_Device_ID(inst, buffer, MAX_DEVICE_ID_LEN + 1, 0);
        if (cr != 0)
            throw new Exception("CM Error:" + cr);

        try
        {
            return Marshal.PtrToStringAnsi(buffer);
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
    }

    public string[] ChildrenPnpDeviceIds
    {
        get
        {
            if (IsVistaOrHiger)
                return GetStringListProperty(DEVPROPKEY.DEVPKEY_Device_Children);

            uint child;
            int cr = CM_Get_Child(out child, _data.DevInst, 0);
            if (cr != 0)
                return new string[0];

            List<string> ids = new List<string>();
            ids.Add(GetDeviceId(child));
            do
            {
                cr = CM_Get_Sibling(out child, child, 0);
                if (cr != 0)
                    return ids.ToArray();

                ids.Add(GetDeviceId(child));
            }
            while (true);
        }
    }

    private static bool IsVistaOrHiger
    {
        get
        {
            return (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.CompareTo(new Version(6, 0)) >= 0);
        }
    }

    private const int INVALID_HANDLE_VALUE = -1;
    private const int ERROR_NO_MORE_ITEMS = 259;
    private const int MAX_DEVICE_ID_LEN = 200;

    [StructLayout(LayoutKind.Sequential)]
    private struct SP_DEVINFO_DATA
    {
        public int cbSize;
        public Guid ClassGuid;
        public uint DevInst;
        public IntPtr Reserved;
    }

    [Flags]
    private enum DIGCF : uint
    {
        DIGCF_DEFAULT = 0x00000001,
        DIGCF_PRESENT = 0x00000002,
        DIGCF_ALLCLASSES = 0x00000004,
        DIGCF_PROFILE = 0x00000008,
        DIGCF_DEVICEINTERFACE = 0x00000010,
    }

    [DllImport("setupapi.dll", SetLastError = true)]
    private static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);

    [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern IntPtr SetupDiGetClassDevs(IntPtr ClassGuid, string Enumerator, IntPtr hwndParent, DIGCF Flags);

    [DllImport("setupapi.dll")]
    private static extern int CM_Get_Parent(out uint pdnDevInst, uint dnDevInst, uint ulFlags);

    [DllImport("setupapi.dll")]
    private static extern int CM_Get_Device_ID(uint dnDevInst, IntPtr Buffer, int BufferLen, uint ulFlags);

    [DllImport("setupapi.dll")]
    private static extern int CM_Get_Child(out uint pdnDevInst, uint dnDevInst, uint ulFlags);

    [DllImport("setupapi.dll")]
    private static extern int CM_Get_Sibling(out uint pdnDevInst, uint dnDevInst, uint ulFlags);

    [DllImport("setupapi.dll")]
    private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

    // vista and higher
    [DllImport("setupapi.dll", SetLastError = true, EntryPoint = "SetupDiGetDevicePropertyW")]
    private static extern bool SetupDiGetDeviceProperty(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref DEVPROPKEY propertyKey, out int propertyType, IntPtr propertyBuffer, int propertyBufferSize, out int requiredSize, int flags);

    [StructLayout(LayoutKind.Sequential)]
    private struct DEVPROPKEY
    {
        public Guid fmtid;
        public uint pid;

        // from devpkey.h
        public static readonly DEVPROPKEY DEVPKEY_Device_Parent = new DEVPROPKEY { fmtid = new Guid("{4340A6C5-93FA-4706-972C-7B648008A5A7}"), pid = 8 };
        public static readonly DEVPROPKEY DEVPKEY_Device_Children = new DEVPROPKEY { fmtid = new Guid("{4340A6C5-93FA-4706-972C-7B648008A5A7}"), pid = 9 };
    }

    private string[] GetStringListProperty(DEVPROPKEY key)
    {
        int type;
        int size;
        SetupDiGetDeviceProperty(_hDevInfo, ref _data, ref key, out type, IntPtr.Zero, 0, out size, 0);
        if (size == 0)
            return new string[0];

        IntPtr buffer = Marshal.AllocHGlobal(size);
        try
        {
            if (!SetupDiGetDeviceProperty(_hDevInfo, ref _data, ref key, out type, buffer, size, out size, 0))
                throw new Win32Exception(Marshal.GetLastWin32Error());

            List<string> strings = new List<string>();
            IntPtr current = buffer;
            do
            {
                string s = Marshal.PtrToStringUni(current);
                if (string.IsNullOrEmpty(s))
                    break;

                strings.Add(s);
                current += (1 + s.Length) * 2;
            }
            while (true);
            return strings.ToArray();
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
    }

    private string GetStringProperty(DEVPROPKEY key)
    {
        int type;
        int size;
        SetupDiGetDeviceProperty(_hDevInfo, ref _data, ref key, out type, IntPtr.Zero, 0, out size, 0);
        if (size == 0)
            return null;

        IntPtr buffer = Marshal.AllocHGlobal(size);
        try
        {
            if (!SetupDiGetDeviceProperty(_hDevInfo, ref _data, ref key, out type, buffer, size, out size, 0))
                throw new Win32Exception(Marshal.GetLastWin32Error());

            return Marshal.PtrToStringUni(buffer);
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
    }
}

我还没有尝试过这个,但Vista或更高版本的操作系统可能会有些问题。这是为了一个将在工业环境中使用的BMS,其中许多运行XP。不过我会看一下的。非常感谢,这看起来非常全面 :) - tmwoods
我已更新了Device类代码以支持Windows XP,但未经测试。 - Simon Mourier
所以我有机会开始使用这个,但我仍然有点迷失下一步该怎么做(我还是一个非常初学者)。我该如何使用GetDiskNames函数?我可以调用它,但我不知道应该使用哪种类型的变量来接收返回值(或者是否我应该这样做)。 - tmwoods
我刚试了这段代码,但好像没有正常工作。Query Win32_DiskDrive 返回的 PNPDeviceID 和 "Device" 类返回的不同。例如,在我的情况下,查询返回类似于 - PNPDeviceID: USBSTOR\DISK&VEN_ABCD&PROD_1234&REV_0001\8&2C3C9390&0,而 Device 类返回实际的 VID/PID 组合 --> USB\VID_4568&PID_QWER&MI_00\7&15b8d7f0&3&0000,有什么想法解决这个问题吗?我的设备是可移动的 USB 大容量存储设备。 - Sredni
1
我无法感谢你发布Device类的足够。我一直担心自己不得不实现所有的PInvoke,而你真的在这方面帮了我很多。非常感激。 - Mr. TA
显示剩余3条评论

1

我曾经遇到过同样的问题,浏览WMI也没有帮助我解决问题。

但是我最终得出了以下几行代码,对我来说非常有效:

private string GetAvailableStorageDrive()
{
    foreach (var info in System.IO.DriveInfo.GetDrives())
    {
        if (info.DriveType == System.IO.DriveType.Removable && 
             info.IsReady && 
            !info.Name.Equals("A:\\"))
        {
            return info.Name;
        }
    }
    return string.Empty;
}

基本上,上述函数查看DriveType是否为Removable,并且检查驱动器是否已准备好。 我还排除了驱动器字母'A',因为在默认的Windows环境中,这是软盘驱动器。
DriveType.Removable的描述: 该驱动器是可移动存储设备,例如软盘驱动器或USB闪存驱动器。 注意:正如CodeCaster指出的那样,此函数还将返回可移动存储设备,例如SATA。 因此,如果是这种情况,您将不得不寻找其他人提供的更复杂的解决方案。

我知道某些读卡器和内部SATA硬盘(或其驱动程序)也会报告自己是可移动磁盘。这些通常不是OP似乎正在寻找的可移动USB大容量存储设备类型。 - CodeCaster
@CodeCaster 很好的观点。在我的环境中,这是完全有意义的,因为我得到了存储设备字母。 如果 OP 对此感到满意,这比其他方法要简单得多(例如 Simon Mourier 的答案)。 - Fabian Bigler
@CodeCaster 这也取决于OP的意图,以及他自己的环境中是否有SATA硬盘。也许他很幸运。 :) - Fabian Bigler
抱歉,这并不是我正在寻找的内容。我是一名定制电子开发人员,我需要在可能存在多个设备的情况下检测我的特定设备。我需要的不仅仅是驱动器字母;我需要确定哪些驱动器字母与特定的VID/PID相关。 - tmwoods
@tmwoods 哎,那很遗憾。我本来希望这对你有帮助。那么Simon Mourier的答案就是你要找的东西了。 - Fabian Bigler

0

有一个古老的Win32 API用于设备枚举,它曾经是安装程序API的一部分,但现在已经不再使用了 - 我只知道它的存在,对于它的使用并不是很了解,但希望它能有所帮助:

http://msdn.microsoft.com/en-us/library/windows/hardware/ff551015(v=vs.85).aspx

SP_DEVICE_INTERFACE_DETAIL 结构是你的最终目标:它拥有实际的设备路径。然而,要到达那里的路径...全部都需要 PInvoke,并且从签名上看相当让人不爽,但以下是一些参考资料:

PInvoke 签名:

http://pinvoke.net/default.aspx/setupapi/SetupDiGetClassDevs.html http://pinvoke.net/default.aspx/setupapi/SetupDiEnumDeviceInterfaces.html http://pinvoke.net/default.aspx/setupapi/SetupDiGetDeviceInterfaceDetail.html

结构映射如下:

http://pinvoke.net/default.aspx/Structures/SP_DEVICE_INTERFACE_DATA.html http://pinvoke.net/default.aspx/Structures/SP_DEVICE_INTERFACE_DETAIL_DATA.html

哦,我找到了一些使用示例(用C++编写的,但是很容易翻译):

http://oroboro.com/usb-serial-number/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接