Device Integration – Map USB Devices / Card Readers to Volume Mount Points

Recently I was tasked with having to resolve the mount point of any given USB device. In my case, I wanted to know the mount point of each slot on a six-slot USB card reader. The tricky part was that while there were six SD card reader slots, we were given only three USB devices to connect to the reader. Essentially, there was no easy 1:1 relationship because each USB device was mapped to two different card slots. Our development team came up with some great solutions (one for Windows, one for Macintosh) for this problem.

Windows

In Windows, we started off by using a C# program called USBView that gave us the topography of USB devices. We narrowed down the list of USB devices to the one we wanted by picking out the USB devices with a specific vendor ID. Then, we used the serial number to get the volume name associated with the device. Once we had the volume name, we could use the registry to look up the mount point (if no mount point existed, we manually mounted the device).

This solution should have worked, but then we realized this method only gave us ONE mount point per device; each device had TWO mount points. Our second solution seemed to be more promising. Instead of using the USBView program to tell us what devices were connected, we decided to head to the Registry and see if we could get any important information there.

In the USBSTOR key (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USBSTOR), we found exactly what we were looking for. Here, each of our USB card readers was listed with a bit of information about each. Of most importance was the ParentIdPrefix String value. This same value appears to be buried in a few of the MountedDevices (HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices) Registry keys.

Why is this important? This ParentIdPrefix allows us to see exactly what hardware device is associated with a volume mount point (on Vista, the serial number of the device serves this same purpose). A few lines of code later, we had a direct mapping between each card slot and its mount point.

I have provided an excerpt of my code below. Client-specific code has been censored and code specific to the rest of the project may not make sense here, but hopefully you get an idea of the logic involved in fetching information from the Registry. Also, don’t forget to state at the top of your file that you are using Microsoft.Win32 and using System.Collections.

// this code finds all of our USB card readers in the Registry
RegistryKey topKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Enum\\USBSTOR\\xxxxxxxx");
string[] topSubKeyNames = topKey.GetSubKeyNames();

// initialize arrays
string[] parentIdPrefixes = new string[topSubKeyNames.Length];
string[] serials = new string[topSubKeyNames.Length];

// this code finds the parent id prefixes for each
// device in the registry
int i = 0;
foreach (string key in topSubKeyNames)
{
RegistryKey tempKey = Registry.LocalMachine.OpenSubKey(“SYSTEM\\CurrentControlSet\\Enum\\USBSTOR\\xxxxxxxx\\” + key);
serials[i] = key;
parentIdPrefixes[i] = Environment.OSVersion.Version.Major == 5 ? (string)tempKey.GetValue(“ParentIdPrefix”) : key;
i++;
}

// the following code searches the registry for mount points that
// match the ParentIdPrefixes we just found
RegistryKey mountedDevices = Registry.LocalMachine.OpenSubKey(“SYSTEM\\MountedDevices”);
string[] mountedValueNames = mountedDevices.GetValueNames();
drives = new Dictionary();

foreach (string keyName in mountedValueNames)
{
if (keyName.StartsWith(“\\??\\”))
{
string keyValue = System.Text.Encoding.UTF8.GetString((byte[])mountedDevices.GetValue(keyName)).Replace(“\0”, “”);
string[] tokenized = keyValue.Split(‘#’);
string currentParentIdPrefix = “”;
if (tokenized.Length >= 3 && tokenized[2].Length >= 3)
{
currentParentIdPrefix = Environment.OSVersion.Version.Major == 5 ? tokenized[2].Remove(tokenized[2].Length – 3) : tokenized[2];
for (int k = 0; k < parentIdPrefixes.Length; k++)
{
if (parentIdPrefixes[k] != null && parentIdPrefixes[k].Equals(currentParentIdPrefix) && serials[k] != null)
{
// find drive letter if possible
string driveLetter = null;
foreach (string innerKeyName in mountedValueNames)
{
if (innerKeyName.StartsWith(“\\DosDevices\\”))
{
string innerKeyValue = System.Text.Encoding.UTF8.GetString((byte[])mountedDevices.GetValue(innerKeyName)).Replace(“\0”, “”);
if (innerKeyValue.Equals(keyValue) && !innerKeyName.Equals(keyName))
{
driveLetter = innerKeyName.Split(‘\\’)[2] + “\\”;
}
}
}
Dictionary holdingDictionary = new Dictionary();
string serialWithoutPort = serials[k].Substring(0, serials[k].Length – 2);
holdingDictionary.Add(“serial”, serials[k]);
holdingDictionary.Add(“volumeName”, keyName);
holdingDictionary.Add(“mountPath”, driveLetter);
if (!drives.ContainsKey(serialWithoutPort))
{
drives.Add(serialWithoutPort, new ArrayList());
}
ArrayList driveList = drives[serialWithoutPort];
driveList.Add(holdingDictionary);
serials[k] = null; // remove the serial so duplicates are not entered
break;
}
}
}
}
}

Macintosh

The solution for Macintosh is much simpler than its Windows counterpart; however, it’s not immediately apparent where all the information is.

ioreg –x –l

This magic function above gives us almost everything we need. I would show you the output, but it’s extremely long and it’s better to see the output with your own hardware than mine. The first thing this command does is it shows us which card readers are plugged in. Then, looking at children of those card readers, you can navigate to each card slot within the card reader (there are two in my case). Each one of these card slots has a BSD name, with which we can execute the command:

diskutil info {bsdName}

{bsdName} is to be replaced with the BSD name you found for each card slot (in my case I had six card slots so I would have to execute this command six times.) This command gives us the last piece to our puzzle, which is the mount point of the specific drive we’re looking at.