4

I need to track the USB insertion and removal events from the C# application so I came up with the following ideas based on the other questions on SO.

I can't use this method

var drives = DriveInfo.GetDrives()
    .Where(drive => drive.IsReady && drive.DriveType == DriveType.Removable).ToList();

because it can lead to a lot of troubles when you need to differentiate between connected and disconnected devices (the new ones can have the same drive letter and name like the ones that was previously cached).

So I decided to use WMI to solve this problem but I found that it's impossible to get the drive letter of the specified USB device via Win32_USBHub class. Then I thought that I can execute another query like this

foreach (ManagementObject device in new ManagementObjectSearcher(@"SELECT * FROM Win32_USBHub").Get())
{
    string deviceID = (string)device.GetPropertyValue("DeviceID");

    Console.WriteLine("{0}", deviceID);

    string query = string.Format("SELECT * FROM Win32_LogicalDisk WHERE DeviceID='{0}'", deviceID);
    foreach (ManagementObject o in new ManagementObjectSearcher(query).Get())
    {
        string name = (string)o.GetPropertyValue("Name");
        Console.WriteLine("{0}", name);
    }

    Console.WriteLine("==================================");
}

but it doesn't work at all -- I get an exception "Invalid query" every time when I try to execute query that works with the Win32_LogicalDisk table.

Why? What am I doing wrong? How can I fix it? Maybe there is a better ways to solve this problem?

Thanks in advance.

FrozenHeart
  • 19,844
  • 33
  • 126
  • 242
  • Does http://stackoverflow.com/questions/6653205/how-can-i-get-the-drive-letter-of-an-usb-device?rq=1 answer your question? – MSalters Aug 11 '15 at 10:03
  • @MSalters `string query = string.Format("ASSOCIATORS OF {{Win32_USBHub.DeviceID = '{0}'}} WHERE AssocClass = Win32_LogicalDisk", deviceID); foreach (var o in new ManagementObjectSearcher(query).Get()) { string name = (string)o.GetPropertyValue("Name"); Console.WriteLine("{0}", name); }` game me an "Invalid object path" exception – FrozenHeart Aug 11 '15 at 10:11

2 Answers2

11

You get an exception because your deviceID contains characters that need to be escaped (backslashes). With simple replace you shouldn't get the exception.

string query = string.Format("SELECT * FROM Win32_LogicalDisk WHERE DeviceID='{0}'", deviceID.Replace(@"\", @"\\"));

However, getting USB drive letter from WMI is a little more complicated. You need to go through a few classes, as stated in a link that @MSalters posted in his comment:

Win32_DiskDrive-> Win32_DiskDriveToDiskPartition -> Win32_DiskPartition -> Win32_LogicalDiskToPartition -> Win32_LogicalDisk.

A little modified version of code found here worked for me:

foreach (ManagementObject device in new ManagementObjectSearcher(@"SELECT * FROM Win32_DiskDrive WHERE InterfaceType LIKE 'USB%'").Get())
{
    Console.WriteLine((string)device.GetPropertyValue("DeviceID"));
    Console.WriteLine((string)device.GetPropertyValue("PNPDeviceID"));                

    foreach (ManagementObject partition in new ManagementObjectSearcher(
        "ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + device.Properties["DeviceID"].Value
        + "'} WHERE AssocClass = Win32_DiskDriveToDiskPartition").Get())
    {
        foreach (ManagementObject disk in new ManagementObjectSearcher(
                    "ASSOCIATORS OF {Win32_DiskPartition.DeviceID='"
                        + partition["DeviceID"]
                        + "'} WHERE AssocClass = Win32_LogicalDiskToPartition").Get())
        {
            Console.WriteLine("Drive letter " + disk["Name"]);
        }
    }
}
Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
michalk
  • 566
  • 4
  • 15
  • 1
    Thanks for the answer. What's the point of using `PNPDeviceID` instead of `DeviceID` btw? And is it the best way to solve the original problem? – FrozenHeart Aug 11 '15 at 12:30
  • I just printed out `PNPDeviceID` for more clarity of output, it isn't used in the process of search. As I understand it, `PNPDeviceID` is in some standard format used by Plug and Play manager, and `DeviceID` format varies from class to class. Is it the best method? I have no idea. But it's certainly one of the shortest, considering the amount of classes one need to go through to extract this information. – michalk Aug 11 '15 at 13:43
1

This is a refactored version of michalk's answer for C# 8.

I used it in combination with the answers for this, Detecting USB drive insertion and removal using windows service and c#, related question

        static IEnumerable<(string deviceId, string pnpDeviceId, string driveLetter)> SelectDeviceInformation()
        {
            foreach (ManagementObject device in SelectDevices())
            {
                var deviceId = (string)device.GetPropertyValue("DeviceID");
                var pnpDeviceId = (string)device.GetPropertyValue("PNPDeviceID");
                var driveLetter = (string)SelectPartitions(device).SelectMany(SelectDisks).Select(disk => disk["Name"]).Single();

                yield return (deviceId, pnpDeviceId, driveLetter);
            }

            static IEnumerable<ManagementObject> SelectDevices() => new ManagementObjectSearcher(
                    @"SELECT * FROM Win32_DiskDrive WHERE InterfaceType LIKE 'USB%'").Get()
                .Cast<ManagementObject>();

            static IEnumerable<ManagementObject> SelectPartitions(ManagementObject device) => new ManagementObjectSearcher(
                    "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=" +
                    "'" + device.Properties["DeviceID"].Value + "'} " +
                    "WHERE AssocClass = Win32_DiskDriveToDiskPartition").Get()
                .Cast<ManagementObject>();

            static IEnumerable<ManagementObject> SelectDisks(ManagementObject partition) => new ManagementObjectSearcher(
                    "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=" +
                    "'" + partition["DeviceID"] + "'" +
                    "} WHERE AssocClass = Win32_LogicalDiskToPartition").Get()
                .Cast<ManagementObject>();
        }
dtwk2
  • 75
  • 6