Reputation: 6226
How can I get a list of all the connected USB devices on a windows computer?
Upvotes: 117
Views: 255759
Reputation: 1471
Many USB Drives these days have some other controller under the USB connection. You might have a USB to IDE, USB to SCSI, USB to SATA, or USB to NVME adapter that completely masks the 3 or 4 devices under it where your logical drive actually shows up, and doesn't show up as removeable nor as USB at all.
For these reasons, I didn't like any of the previous answers, though many gave me hints that resulted in this code. This code uses a Win32_PnpDevice WMI method GetDeviceProperties
to get extended properties of the PnpDevice. One of those extended properties is DEVPKEY_Device_Parent
which gives the WMI DeviceID of the parent device. I was able to recursively enumerate parent devices all the way back to the host computer.
The simple psuedo-code is this:
I appologize that the c# is so long. I tried to comment as best I could. The method to focus on is GetUSBAttachedDriveLetters()
. You can copy all the static methods out to your own project and along with a few standard using statments, you should be good to go.
Using statements:
using System;
using System.Collections.Generic;
using System.Management;
using System.Linq;
using System.Threading.Tasks;
Here's my C# code:
/// <summary>
/// Uses WMI Classes to find drives and checks their Device connection tree to see
/// if USB is part of the connection tree. This catches drives that are attached
/// through USB, but don't self report as a USB or removeable drive.
/// </summary>
/// <returns></returns>
public static string[] GetUSBAttachedDriveLetters()
{
List<string> usbAttachedDrives = new List<string>();
List<Task> LogicalDriveTasks = new List<Task>();
object listLock = new object();
string scope = @"\\.\root\CIMV2";
// Find all of the LogicalDiskToPartion WMI object converters. This will find
// all of the Logical Drive letters on the system that are connected to a physical
// disk partition, and provide the WMI IDs for each Logical Disk and matching Partitions.
string query = $"Select Antecedent, Dependent From Win32_LogicalDiskToPartition";
ManagementObject[] LD2Ps;
using (ManagementObjectSearcher moSearch = new ManagementObjectSearcher(scope, query))
{
LD2Ps = moSearch.Get().OfType<ManagementObject>().ToArray();
}
// Loop through all logical drives found on the system, and start a task thread to check each one in parallel for speed.
foreach (ManagementObject LD2P in LD2Ps)
{
LogicalDriveTasks.Add(Task.Run(() =>
{
string PartitionID = LD2P["Antecedent"].ToString();
string LogicalDiskID = LD2P["Dependent"].ToString();
// Get the DiskDriveToDiskPartition WMI object converter for this logical drive.
ManagementObject DD2P;
query = $"Select Antecedent, Dependent From Win32_DiskDriveToDiskPartition Where Dependent=\"{PartitionID.Replace("\\", "\\\\").Replace("\"", "\\\"")}\"";
using (ManagementObjectSearcher moSearch = new ManagementObjectSearcher(scope, query))
{
// While WMI queries can return multiple items, we should only get 1 from the unique partiton id provided.
DD2P = moSearch.Get().OfType<ManagementObject>().FirstOrDefault();
}
// Get the WMI DiskDrive object for this logical drive, and get its
// PNPDeviceID, a unique identifier for every Windows device.
string DiskDriveID = DD2P["Antecedent"].ToString();
ManagementObject DD = new ManagementObject(DiskDriveID);
string DD_PNPDeviceID = DD["PNPDeviceID"].ToString();
// If any of the devices on the device tree are "USB" then this is a USBAttachedDrive and should be added to the result.
if (DeviceIsUSB(DD_PNPDeviceID))
{
// The drive letter to add comes from WMI Logical Disk object, Name property.
ManagementObject LD = new ManagementObject(LogicalDiskID);
string LD_DriveLetter = LD["Name"].ToString();
lock (listLock) usbAttachedDrives.Add(LD_DriveLetter);
}
}));
}
Task.WaitAll(LogicalDriveTasks.ToArray());
usbAttachedDrives.Sort();
return usbAttachedDrives.ToArray();
}
/// <summary>
/// Uses WMI Classes to get a PNPDevice's Parent DeviceID.
/// </summary>
/// <param name="DeviceID"></param>
/// <returns></returns>
public static string GetParentDeviceID(string DeviceID)
{
// Open the WMI Object as a PNPEntity (selecting only the key property DeviceID, instead of * goes much faster).
ManagementObject mo;
string query = $"Select DeviceID From Win32_PnPEntity Where DeviceID=\"{DeviceID.Replace("\\", "\\\\").Replace("\"", "\\\"")}\"";
using (ManagementObjectSearcher moSearch = new ManagementObjectSearcher(@"\\.\root\CIMV2", query))
{
mo = moSearch.Get().OfType<ManagementObject>().ToArray().FirstOrDefault();
}
// The WMI Win32_PnpEntity class has a GetDeviceProperties method that can be called for extended properties.
// Call that method to get the DEVPKEY_Device_Parent property, which contains the ParentDeviceID in it's Data sub-property.
ManagementBaseObject inParams = new ManagementClass("Win32_PnPEntity").GetMethodParameters("GetDeviceProperties");
inParams["devicePropertyKeys"] = new string[] { "DEVPKEY_Device_Parent" };
ManagementBaseObject outParams = mo.InvokeMethod("GetDeviceProperties", inParams, null);
ManagementBaseObject[] deviceProperties = (ManagementBaseObject[])(outParams?.Properties["deviceProperties"].Value);
ManagementBaseObject parentDeviceProperty = deviceProperties.FirstOrDefault();
// Return the DeviceName and the ParentDeviceID.
// Not all devices have names and/or parents, so handle potential null values.
object parentDeviceID = null;
foreach (PropertyData pd in parentDeviceProperty.Properties)
{
if (pd.Name == "Data") parentDeviceID = pd.Value;
}
return parentDeviceID?.ToString();
}
/// <summary>
/// Recursively searches through a devices connection tree and returns TRUE as soon as a USB device is found.
/// If it reaches the host computer and no USB device is found, returns FALSE.
/// </summary>
/// <param name="DeviceID"></param>
/// <returns></returns>
public static bool DeviceIsUSB(string DeviceID)
{
if (DeviceID.ToUpper().Contains("USB")) return true;
string thisParentID = GetParentDeviceID(DeviceID);
if (string.IsNullOrWhiteSpace(thisParentID)) return false;
return DeviceIsUSB(thisParentID); ;
}
Upvotes: 0
Reputation: 2683
This improves the answer that Tydaeus wrote: https://stackoverflow.com/a/48390286/4675770
The question asks "How can I get a list of all the connected USB devices on a windows computer?" and a USB hub, for example, is not exactly what I would want on that list.
So I added an example how to exclude a class of devices, for example USB hubs.
The list of classes like
https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-mouse
https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-keyboard
but there are also other possible classes, like USB hubs, for example:
https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-usb-hub
It also displays the device VID (vendor ID), PID (product ID) and serial number, if anyone is interested.
using System;
using System.Collections.Generic;
using System.Management;
/*
ClassGuid for USB hubs: {36fc9e60-c465-11cf-8056-444553540000} The identifier is GUID_DEVINTERFACE_USB_HUB
ClassGuid for network adapters: {4d36e972-e325-11ce-bfc1-08002be10318} The identifier is GUID_DEVINTERFACE_NET
ClassGuid for HID devices: {745a17a0-74d3-11d0-b6fe-00a0c90f57da} The identifier is GUID_DEVINTERFACE_HID
ClassGuid for mice: {4d36e96f-e325-11ce-bfc1-08002be10318} The identifier is GUID_DEVINTERFACE_MOUSE
ClassGuid for keyboards: {4d36e96b-e325-11ce-bfc1-08002be10318} The identifier is GUID_DEVINTERFACE_KEYBOARD
https://learn.microsoft.com/en-us/windows-hardware/drivers/install/system-defined-device-setup-classes-available-to-vendors
/**/
class Program
{
static void Main(string[] args)
{
IList<ManagementBaseObject> usbDevices = GetUsbDevices();
foreach (ManagementBaseObject usbDevice in usbDevices)
{
string classGuid = (string)usbDevice["ClassGuid"];
// you can exclude a device class, for example USB hubs:
if (classGuid == "{36fc9e60-c465-11cf-8056-444553540000}")
continue;
Console.WriteLine("----- DEVICE -----");
foreach (var property in usbDevice.Properties)
{
if(property.Name == "DeviceID")
Console.WriteLine(string.Format("{0}: {1}", property.Name, property.Value));
}
string deviceID = (string)usbDevice["DeviceID"];
if (deviceID.Contains("VID_") && deviceID.Contains("PID_"))
{
string[] splitDeviceID = deviceID.Split('\\');
string[] splitVidPid = splitDeviceID[splitDeviceID.Length - 2].Split('&');
string vid = splitVidPid[0].Split('_')[1];
string pid = splitVidPid[1].Split('_')[1];
string serialNumber = splitDeviceID[splitDeviceID.Length - 1];
Console.WriteLine("VID: {0}", vid);
Console.WriteLine("PID: {0}", pid);
Console.WriteLine("Serial Number: {0}", serialNumber);
}
Console.WriteLine("------------------");
}
}
public static IList<ManagementBaseObject> GetUsbDevices()
{
IList<string> usbDeviceAddresses = LookUpUsbDeviceAddresses();
List<ManagementBaseObject> usbDevices = new List<ManagementBaseObject>();
foreach (string usbDeviceAddress in usbDeviceAddresses)
{
// query MI for the PNP device info
// address must be escaped to be used in the query; luckily, the form we extracted previously is already escaped
ManagementObjectCollection curMoc = QueryMi("Select * from Win32_PnPEntity where PNPDeviceID = " + usbDeviceAddress);
foreach (ManagementBaseObject device in curMoc)
{
usbDevices.Add(device);
}
}
return usbDevices;
}
public static IList<string> LookUpUsbDeviceAddresses()
{
// this query gets the addressing information for connected USB devices
ManagementObjectCollection usbDeviceAddressInfo = QueryMi(@"Select * from Win32_USBControllerDevice");
List<string> usbDeviceAddresses = new List<string>();
foreach (var device in usbDeviceAddressInfo)
{
string curPnpAddress = (string)device.GetPropertyValue("Dependent");
// split out the address portion of the data; note that this includes escaped backslashes and quotes
curPnpAddress = curPnpAddress.Split(new String[] { "DeviceID=" }, 2, StringSplitOptions.None)[1];
usbDeviceAddresses.Add(curPnpAddress);
}
return usbDeviceAddresses;
}
// run a query against Windows Management Infrastructure (MI) and return the resulting collection
public static ManagementObjectCollection QueryMi(string query)
{
ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(query);
ManagementObjectCollection result = managementObjectSearcher.Get();
managementObjectSearcher.Dispose();
return result;
}
}
Upvotes: 2
Reputation: 8937
Add a reference to System.Management for your project, then try something like this:
using System;
using System.Collections.Generic;
using System.Management; // need to add System.Management to your project references.
class Program
{
static void Main(string[] args)
{
var usbDevices = GetUSBDevices();
foreach (var usbDevice in usbDevices)
{
Console.WriteLine(
$"Device ID: {usbDevice.DeviceID}, PNP Device ID: {usbDevice.PnpDeviceID}, Description: {usbDevice.Description}");
}
Console.Read();
}
static List<USBDeviceInfo> GetUSBDevices()
{
List<USBDeviceInfo> devices = new List<USBDeviceInfo>();
using var searcher = new ManagementObjectSearcher(
@"Select * From Win32_USBHub");
using ManagementObjectCollection collection = searcher.Get();
foreach (var device in collection)
{
devices.Add(new USBDeviceInfo(
(string)device.GetPropertyValue("DeviceID"),
(string)device.GetPropertyValue("PNPDeviceID"),
(string)device.GetPropertyValue("Description")
));
}
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; }
}
Upvotes: 139
Reputation: 1067
lstResult.Clear();
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())
{
foreach (var item in disk.Properties)
{
object value = disk.GetPropertyValue(item.Name);
}
string valor = disk["Name"].ToString();
lstResult.Add(valor);
}
}
}
}
Upvotes: 1
Reputation: 9091
I know I'm replying to an old question, but I just went through this same exercise and found out a bit more information, that I think will contribute a lot to the discussion and help out anyone else who finds this question and sees where the existing answers fall short.
The accepted answer is close, and can be corrected using Nedko's comment to it. A more detailed understanding of the WMI Classes involved helps complete the picture.
Win32_USBHub
returns only USB Hubs. That seems obvious in hindsight but the discussion above misses it. It does not include all possible USB devices, only those which can (in theory, at least) act as a hub for additional devices. It misses some devices that are not hubs (particularly parts of composite devices).
Win32_PnPEntity
does include all the USB devices, and hundreds more non-USB devices. Russel Gantman's advice to use a WHERE clause search Win32_PnPEntity
for a DeviceID beginning with "USB%" to filter the list is helpful but slightly incomplete; it misses bluetooth devices, some printers/print servers, and HID-compliant mice and keyboards. I have seen "USB\%", "USBSTOR\%", "USBPRINT\%", "BTH\%", "SWD\%", and "HID\%". Win32_PnPEntity
is, however, a good "master" reference to look up information once you are in possession of the PNPDeviceID from other sources.
What I found was the best way to enumerate USB devices was to query Win32_USBControllerDevice
. While it doesn't give detailed information for the devices, it does completely enumerate your USB devices and gives you an Antecedent/Dependent pair of PNPDeviceID
s for every USB Device (including Hubs, non-Hub devices, and HID-compliant devices) on your system. Each Dependent returned from the query will be a USB Device. The Antecedent will be the Controller it is assigned to, one of the USB Controllers returned by querying Win32_USBController
.
As a bonus, it appears that under the hood, WMI walks the Device Tree when responding to the Win32_USBControllerDevice
query, so the order in which these results are returned can help identify parent/child relationships. (This is not documented and is thus only a guess; use the SetupDi API's CM_Get_Parent (or Child + Sibling) for definitive results.) As an option to the SetupDi API, it appears that for all the devices listed under Win32_USBHub
they can be looked up in the registry (at HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\ + PNPDeviceID
) and will have a parameter ParentIdPrefix
which will be the prefix of the last field in the PNPDeviceID of its children, so this could also be used in a wildcard match to filter the Win32_PnPEntity
query.
In my application, I did the following:
Win32_PnPEntity
and stored the results in a key-value map (with PNPDeviceID as the key) for later retrieval. This is optional if you want to do individual queries later.Win32_USBControllerDevice
for a definitive list of USB devices on my system (all the Dependents) and extracted the PNPDeviceIDs of these. I went further, based on order following the device tree, to assign devices to the root hub (the first device returned, rather than the controller) and built a tree based on the parentIdPrefix. The order the query returns, which matches device tree enumeration via SetupDi, is each root hub (for whom the Antecedent identifies the controller), followed by an iteration of devices under it, e.g., on my system:
Win32_USBController
. This gave me the detailed information of the PNPDeviceIDs of my controllers which are at the top of the device tree (which were the Antecedents of the previous query). Using the tree derived in the previous step, recursively iterated over its children (the root hubs) and their children (the other hubs) and their children (non-hub devices and composite devices) and their children, etc.
Win32_PnPEntity
individually using the PNPDeviceId to get the information at this step; probably a cpu vs. memory tradeoff determining which order is better.)In summary, Win32USBControllerDevice
Dependents are a complete list of USB Devices on a system (other than the Controllers themselves, which are the Antecedents in that same query), and by cross-referencing these PNPDeviceId
pairs with information from the registry and from the other queries mentioned, a detailed picture can be constructed.
Upvotes: 62
Reputation: 1605
Adel Hazzah's answer gives working code, Daniel Widdis's and Nedko's comments mention that you need to query Win32_USBControllerDevice and use its Dependent property, and Daniel's answer gives a lot of detail without code.
Here's a synthesis of the above discussion to provide working code that lists the directly accessible PNP device properties of all connected USB devices:
using System;
using System.Collections.Generic;
using System.Management; // reference required
namespace cSharpUtilities
{
class UsbBrowser
{
public static void PrintUsbDevices()
{
IList<ManagementBaseObject> usbDevices = GetUsbDevices();
foreach (ManagementBaseObject usbDevice in usbDevices)
{
Console.WriteLine("----- DEVICE -----");
foreach (var property in usbDevice.Properties)
{
Console.WriteLine(string.Format("{0}: {1}", property.Name, property.Value));
}
Console.WriteLine("------------------");
}
}
public static IList<ManagementBaseObject> GetUsbDevices()
{
IList<string> usbDeviceAddresses = LookUpUsbDeviceAddresses();
List<ManagementBaseObject> usbDevices = new List<ManagementBaseObject>();
foreach (string usbDeviceAddress in usbDeviceAddresses)
{
// query MI for the PNP device info
// address must be escaped to be used in the query; luckily, the form we extracted previously is already escaped
ManagementObjectCollection curMoc = QueryMi("Select * from Win32_PnPEntity where PNPDeviceID = " + usbDeviceAddress);
foreach (ManagementBaseObject device in curMoc)
{
usbDevices.Add(device);
}
}
return usbDevices;
}
public static IList<string> LookUpUsbDeviceAddresses()
{
// this query gets the addressing information for connected USB devices
ManagementObjectCollection usbDeviceAddressInfo = QueryMi(@"Select * from Win32_USBControllerDevice");
List<string> usbDeviceAddresses = new List<string>();
foreach(var device in usbDeviceAddressInfo)
{
string curPnpAddress = (string)device.GetPropertyValue("Dependent");
// split out the address portion of the data; note that this includes escaped backslashes and quotes
curPnpAddress = curPnpAddress.Split(new String[] { "DeviceID=" }, 2, StringSplitOptions.None)[1];
usbDeviceAddresses.Add(curPnpAddress);
}
return usbDeviceAddresses;
}
// run a query against Windows Management Infrastructure (MI) and return the resulting collection
public static ManagementObjectCollection QueryMi(string query)
{
ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(query);
ManagementObjectCollection result = managementObjectSearcher.Get();
managementObjectSearcher.Dispose();
return result;
}
}
}
You'll need to add exception handling if you want it. Consult Daniel's answer if you want to figure out the device tree and such.
Upvotes: 18
Reputation: 2053
This is a much simpler example for people only looking for removable usb drives.
using System.IO;
foreach (DriveInfo drive in DriveInfo.GetDrives())
{
if (drive.DriveType == DriveType.Removable)
{
Console.WriteLine(string.Format("({0}) {1}", drive.Name.Replace("\\",""), drive.VolumeLabel));
}
}
Upvotes: 9
Reputation: 309
If you change the ManagementObjectSearcher to the following:
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
@"SELECT * FROM Win32_PnPEntity where DeviceID Like ""USB%""");
So the "GetUSBDevices() looks like this"
static List<USBDeviceInfo> GetUSBDevices()
{
List<USBDeviceInfo> devices = new List<USBDeviceInfo>();
ManagementObjectCollection collection;
using (var searcher = new ManagementObjectSearcher(@"SELECT * FROM Win32_PnPEntity where DeviceID Like ""USB%"""))
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;
}
}
Your results will be limited to USB devices (as opposed to all types on your system)
Upvotes: 10
Reputation: 3259
To see the devices I was interested in, I had replace Win32_USBHub
by Win32_PnPEntity
in Adel Hazzah's code, based on this post. This works for me:
namespace ConsoleApplication1
{
using System;
using System.Collections.Generic;
using System.Management; // need to add System.Management to your project references.
class Program
{
static void Main(string[] args)
{
var usbDevices = GetUSBDevices();
foreach (var usbDevice in usbDevices)
{
Console.WriteLine("Device ID: {0}, PNP Device ID: {1}, Description: {2}",
usbDevice.DeviceID, usbDevice.PnpDeviceID, usbDevice.Description);
}
Console.Read();
}
static List<USBDeviceInfo> GetUSBDevices()
{
List<USBDeviceInfo> devices = new List<USBDeviceInfo>();
ManagementObjectCollection collection;
using (var searcher = new ManagementObjectSearcher(@"Select * From Win32_PnPEntity"))
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; }
}
}
Upvotes: 19
Reputation: 1038780
You may find this thread useful. And here's a google code project exemplifying this (it P/Invokes into setupapi.dll
).
Upvotes: 2