Find Windows Drive Letter of a removable disk from USB VID/PID - c#

This question has been asked before, and there is one answer that supposedly works here. But I've tried it out and it does not work for me.
The issue is that the PNPDeviceID returned by the Query on Win32_DiskDrive and that returned by the "Device" class are different. For example in my case the Query returns something like - PNPDeviceID: USBSTOR\DISK&VEN_ABCD&PROD_1234&REV_0001\8&2C3C9390&0 while the Device class returns the actual VID/PID combination --> USB\VID_4568&PID_QWER&MI_00\7&15b8d7f0&3&0000.
So the SELECT query on Win32_DiskDrive always fails.
Main Code:
var usbDevices = GetUSBDevices();
//Enumerate the USB devices to see if any have specific VID/PID
foreach (var usbDevice in usbDevices)
{
if (usbDevice.DeviceID.Contains("ABCD") && usbDevice.DeviceID.Contains("1234"))
{
foreach (string name in usbDevice.GetDiskNames())
{
//Open dialog to show file names
Debug.WriteLine(name);
}
}
}
USBDeviceInfo Class
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)
Debug.WriteLine(childDeviceId.Replace(#"\", #"\\") );
foreach (ManagementObject drive in new ManagementObjectSearcher("SELECT DeviceID FROM Win32_DiskDrive WHERE PNPDeviceID='" + childDeviceId.Replace(#"\", #"\\") + "'").Get())
{
foreach (PropertyData usb in drive.Properties){
if (usb.Value != null && usb.Value.ToString() != "")
{
Debug.Write(usb.Name + "=");
Debug.Write(usb.Value + "\r\n");
}
}
// 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"];
}
}
}
}
}
}
As a side note, I am able to get the Query using "Model" property pass and then find the drive letter, as explained here. But I'm looking for a solution that can tie VID/PID to the drive letter.

I had exactly the same problem and after a hard week of work I finally got the solution.
I also get two links for one device (the link with the vid and pid and a link, which contains "USBSTOR...."). I think it's not the best way to solve the problem, but (until now) it works.
I used two functions:
The first one is to find a USBHub with a specific VID and PID and also find the related second link ("USBSTOR...."). This link is important, because it forms the connection to the drive letter.
A list named "USBobjects" contains a number of related links (USBHub, USBSTOR,....), which refer to all attached devices. I found out, that the USBSTOR link appears right after the link, which contains the VID and PID.
I stored the "USBStOR..." link as a string and used it for the second function to find the related PNPEntity of the DiskDrive. This leads to the correct DiskPartition and furthermore to the LogicalDisk = drive letter.
Hopefully it's not to late and both functions will help u!
FIRST FUNCTION:
public void FindPath()
{
foreach (ManagementObject entity in new ManagementObjectSearcher("select * from Win32_USBHub Where DeviceID Like '%VID_XXXX&PID_XXXX%'").Get())
{
Entity = entity["DeviceID"].ToString();
foreach (ManagementObject controller in entity.GetRelated("Win32_USBController"))
{
foreach (ManagementObject obj in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_USBController.DeviceID='" + controller["PNPDeviceID"].ToString() + "'}").Get())
{
if(obj.ToString().Contains("DeviceID"))
USBobjects.Add(obj["DeviceID"].ToString());
}
}
}
int VidPidposition = USBobjects.IndexOf(Entity);
for (int i = VidPidposition; i <= USBobjects.Count; i++ )
{
if (USBobjects[i].Contains("USBSTOR"))
{
Secondentity = USBobjects[i];
break;
}
}
}
>
SECOND FUNCTION:
public void GetDriveLetter()
{
foreach (ManagementObject drive in new ManagementObjectSearcher("select * from Win32_DiskDrive").Get())
{
if (drive["PNPDeviceID"].ToString() == Secondentity)
{
foreach (ManagementObject o in drive.GetRelated("Win32_DiskPartition"))
{
foreach (ManagementObject i in o.GetRelated("Win32_LogicalDisk"))
{
Console.WriteLine("Disk: " + i["Name"].ToString());
}
}
}
}
}
>

many thanks to #Michaela for his very helpful answer, but I think this code is shorter
public void FindPath()
{
ManagementObjectSearcher entity = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive");
foreach (ManagementObject obj in entity.Get())
{
if (obj["PNPDeviceID"].ToString().Contains("USBSTOR"))
{
if (!USBobjects.Contains(obj["PNPDeviceID"].ToString()))
USBobjects.Add(obj["PNPDeviceID"].ToString());
}
}
}
I have another problem with GetDriveLetter() method, sometimes it takes too long to compile foreach line. can somebody tell me the reason?

DriveInfo[] ListDrives = DriveInfo.GetDrives();
foreach (DriveInfo Drive in ListDrives)
{
if (Drive.DriveType == DriveType.Removable)
{
try
{
Console.WriteLine(Drive.Name);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}

Related

How to get the drive letter of an SD Card connected to a PC, using C# [duplicate]

This question already has answers here:
SD card directory
(1 answer)
How to differentiate between USB device types?
(3 answers)
Closed 1 year ago.
How to get the drive letter of an SD Card connected to a PC, from a C# .NET Framework application?
I have looked at suggested questions on this topic, including this, this & this, but none of them give me the solution I need.
Using System.IO.DriveInfo.GetDrives() or System.Management.ManagementObjectSearcher() with query "Win32_LogicalDisk", I can get the drive letters of all devices, but I can't tell which device(s) is the SD card.
Using System.Management.ManagementObjectSearcher() with query "CIM_LogicalDevice", "Caption = 'SDHC Card'", I get 2 devices with the "SDHC Card" caption property, but no drive letters.
How can I get the drive letter of the SD Card or card reader?
Here is what I have tried so far:
using System;
using System.Management;
namespace Code3
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("\tfrom: 'ManagementObjectSearcher()' with query \"Win32_LogicalDisk\"");
var searcher1 = new ManagementObjectSearcher(#"\root\cimv2", "SELECT * FROM Win32_LogicalDisk");
foreach (ManagementBaseObject disk in searcher1.Get())
{
string diskID = disk.GetPropertyValue("DeviceID").ToString();
int driveType = Convert.ToInt32(disk.GetPropertyValue("DriveType"));
string diskCaption = disk.GetPropertyValue("Caption").ToString();
string diskDescription = disk.GetPropertyValue("Description").ToString();
string diskName = disk.GetPropertyValue("Name").ToString();
int diskMediaType = Convert.ToInt32(disk.GetPropertyValue("MediaType"));
Console.WriteLine($"{diskName} - ID: {diskID}, Caption: {diskCaption}, Desc.: {diskDescription,-16}, Drive Type: {driveType}, Media Type: {diskMediaType}.");
}
Console.WriteLine();
Console.WriteLine("\tfrom: 'ManagementObjectSearcher()' with query SelectQuery(\"CIM_LogicalDevice\", \"Caption = 'SDHC Card'\")");
ManagementScope mgmtScope = new ManagementScope(#"\root\cimv2");
SelectQuery devQuery = new SelectQuery("CIM_LogicalDevice", "Caption = 'SDHC Card'");
var searcher2 = new ManagementObjectSearcher(mgmtScope, devQuery);
foreach (ManagementBaseObject device in searcher2.Get())
{
Console.WriteLine($"{device.GetPropertyValue("Name"),-15} - Caption: {device.GetPropertyValue("Caption")}, Device ID: {device.GetPropertyValue("DeviceID")}.");
continue; // ... to skip property display
if (!string.IsNullOrEmpty(device.GetPropertyValue("Name").ToString()))
{
PropertyDataCollection props = device.Properties;
Console.WriteLine($"\n\t\tProperties of {device.GetPropertyValue("DeviceID")} Drive: \n");
foreach (var prop in device.Properties)
{
if (prop.Value != null)
Console.WriteLine($"{prop.Name,-20} - {prop.Type,-8} - {prop.Value ?? "(null)"}");
}
Console.WriteLine();
}
}
Console.ReadKey();
}
}
}
Thank you for any help you can give me.
EDIT:
From "CIM_LogicalDisk", I can see that "F:" drive is my SD-Card. (from 'VolumeName' property.)
From "CIM_LogicalDevice", I can see the "\.\PHYSICALDRIVE1" and "PCISTOR\DISK&VEN_RSPER&PROD_RTS5208LUN0&REV_1.00\0000" is my SD-Card. (from 'Name', 'Caption', and/or 'Model' properties.)
But my app can't see this! Note that 'drive letter' and 'PHYSICALDRIVE number' do not remain correlated, and can change as different removable devices are inserted and removed.
How can I get my code to make the connection between logical and physical drives?
If you are really sure that your SD card's volume label is always "SDHC Card" (which I am not), then you can use the following approach:
The DriveInfo class contains a static method GetDrives which returns an array of DriveInfo instances. Each instance itself represents on logical drive. You can use the VolumeLabel property to check the name of the volume.
So something like...
var drives = DriveInfo.GetDrives().Where(drive => drive.VolumeLabel == "SDHC Card");
...returns all drives where the volume is called "SDHC Card".
If you want to get the drive letter of the logical drive you can access it by using the RootDirectory property of a concrete instance.
Like:
var drives = DriveInfo.GetDrives().Where(drive => drive.VolumeLabel == "SDHC Card");
foreach (var drive in drives)
Console.WriteLine(drive.RootDirectory.FullName);
I finally got a solution worked out. Using WMI association classes, I was able to make the connection between logical and physical drives.
This class is my solution:
using System.Collections.Generic;
using System.Management;
namespace GetSDCard
{
public class GetSDCards
{
public Card[] GetCards()
{
return FindCards().ToArray();
}
private List<Card> FindCards()
{
List<Card> cards = new List<Card>();
// Get Disk Drives collection (Win32_DiskDrive)
string queryDD = "SELECT * FROM Win32_DiskDrive WHERE Caption = 'SDHC Card'";
using (ManagementObjectSearcher searchDD = new ManagementObjectSearcher(queryDD))
{
ManagementObjectCollection colDiskDrives = searchDD.Get();
foreach (ManagementBaseObject objDrive in colDiskDrives)
{
// Get associated Partitions collection (Win32_DiskDriveToDiskPartition)
string queryPart = $"ASSOCIATORS OF {{Win32_DiskDrive.DeviceID='{objDrive["DeviceID"]}'}} WHERE AssocClass = Win32_DiskDriveToDiskPartition";
using (ManagementObjectSearcher searchPart = new ManagementObjectSearcher(queryPart))
{
ManagementObjectCollection colPartitions = searchPart.Get();
foreach (ManagementBaseObject objPartition in colPartitions)
{
// Get associated Logical Disk collection (Win32_LogicalDiskToPartition)
string queryLD = $"ASSOCIATORS OF {{Win32_DiskPartition.DeviceID='{objPartition["DeviceID"]}'}} WHERE AssocClass = Win32_LogicalDiskToPartition";
using (ManagementObjectSearcher searchLD = new ManagementObjectSearcher(queryLD))
{
ManagementObjectCollection colLogicalDisks = searchLD.Get();
foreach (ManagementBaseObject objLogicalDisk in colLogicalDisks)
cards.Add(new Card($"{objLogicalDisk["DeviceID"]}", $"{objDrive["Caption"]}", $"{objLogicalDisk["VolumeName"]}"));
}
}
}
}
}
return cards;
}
public class Card
{
public string Drive { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public Card(string _drive, string _name, string _label)
{
Drive = _drive;
Name = _name;
Label = _label;
}
}
}
}
Here is a simple console app to demonstrate how to use it.
using GetSDCard;
using System;
using System.IO;
namespace FindSDCard_Demo
{
class Program
{
static void Main(string[] args)
{
GetSDCards getter = new GetSDCards();
GetSDCards.Card[] sdCards = getter.GetCards();
if (sdCards.Length == 0)
Console.WriteLine("No SD Cards found.");
else
{
string sdDrive = sdCards[0].Drive;
Console.WriteLine($"Root folder of SD Card '{sdDrive}':");
foreach (var folder in Directory.GetDirectories(sdDrive))
Console.WriteLine($"\t{folder}");
}
}
}
}
I hope that this can save you the hours of frustration I went through.

How to get CD/DVD-ROM Drive Letter by Serial Number

I am attempting to interface with a Primera Disc Duplicator using their provided PTRobot API. Their API returns information about the recorder drives in the robotic, but the crucial piece missing is the drive letter.
The info they do return is the Model Name, Firmware, and Serial Number.
I need to differentiate between multiple same drives in a unit, and the Serial Number is the only unique value provided.
I have found many examples going the other way around (using drive letter to get the model or serial), but none of them look able to be flipped around for my use.
It sounds like you could get the drive whose serial number matches the one you're searching for, then get it's partitions, and for each partition get it's drive letter from the logical drive.
For example:
using System.Collections.Generic
using System.Management;
public static List<string> GetDriveLettersForSerialNumber(string driveSerialNumber)
{
var results = new List<string>();
if (driveSerialNumber == null) return results;
var drive = new ManagementObjectSearcher(
"SELECT DeviceID, SerialNumber, Partitions FROM Win32_DiskDrive").Get()
.Cast<ManagementObject>()
.FirstOrDefault(device =>
device["SerialNumber"].ToString().Trim()
.Equals(driveSerialNumber.Trim(), StringComparison.OrdinalIgnoreCase));
if (drive == null) return results;
var partitions = new ManagementObjectSearcher(
$"ASSOCIATORS OF {{Win32_DiskDrive.DeviceID='{drive["DeviceID"]}'}} " +
"WHERE AssocClass = Win32_DiskDriveToDiskPartition").Get();
foreach (var partition in partitions)
{
var logicalDrives = new ManagementObjectSearcher(
"ASSOCIATORS OF {{Win32_DiskPartition.DeviceID=" +
$"'{partition["DeviceID"]}'}} " +
"WHERE AssocClass = Win32_LogicalDiskToPartition").Get();
foreach (var logicalDrive in logicalDrives)
{
var volumes = new ManagementObjectSearcher(
"SELECT Name FROM Win32_LogicalDisk WHERE " +
$"Name='{logicalDrive["Name"]}'").Get().Cast<ManagementObject>();
results.AddRange(volumes.Select(v => v["Name"].ToString()));
}
}
return results;
}
For CDROM it seems much easier - both "Id" and "SerialNumber" are contained in the same object:
public static string GetDriveLetterForCDROMSerialNumber(string driveSerialNumber)
{
return new ManagementObjectSearcher(
"SELECT Id, SerialNumber FROM Win32_CDROMDrive").Get()
.Cast<ManagementObject>()
.Where(drive => drive.GetPropertyValue("SerialNumber").ToString().Trim()
.Equals(driveSerialNumber.Trim(), StringComparison.OrdinalIgnoreCase))
.Select(drive => drive.GetPropertyValue("Id").ToString())
.FirstOrDefault() ?? "Unknown";
}
You could write a routine to build a dictionary of drives hashed by serial number by checking each drive. Then you have the missing information needed to work with the PTRobot api.
Edit:
From a search for c# getting a serial number for a drive
Code from an example of how to get the hard drive serial number. UNtested as I no longer have a windows device
Following can help you:
searcher = new
ManagementObjectSearcher("SELECT * FROM Win32_PhysicalMedia");
int i = 0;
foreach(ManagementObject wmi_HD in searcher.Get())
{
// get the hard drive from collection
// using index
HardDrive hd = (HardDrive)hdCollection[i];
// get the hardware serial no.
if (wmi_HD["SerialNumber"] == null)
hd.SerialNo = "None";
else
hd.SerialNo = wmi_HD["SerialNumber"].ToString();
++i;
}
Thanks for the suggestions and pointing me to use WMI queries. It was just a matter of finding which one had the information I needed (Win32_CDROMDrive). Here is my working code:
public static string GetDriveLetter(string serialNum){
if (serialNum != null)
{
var moc = new ManagementObjectSearcher("SELECT SerialNumber, Drive FROM Win32_CDROMDrive");
foreach(var mo in moc.Get())
{
string driveSerial = (string)mo.GetPropertyValue("SerialNumber");
if (driveSerial != null)
{
if (driveSerial.Trim().Equals(serialNum.Trim(), StringComparison.OrdinalIgnoreCase))
{
return (string)mo.GetPropertyValue("Drive");
}
}
}
}
return "Unknown";
}

How to know in C# that program is running on Parallels on a Mac running XP SP2 or VmWare on a Mac running Windows 7

I am using the following code to indicate if code is executed on virtual machine.
using (var searcher = new ManagementObjectSearcher("Select * from Win32_ComputerSystem"))
{
using (var items = searcher.Get())
{
foreach (var item in items)
{
string manufacturer = item["Manufacturer"].ToString().ToLower();
Console.WriteLine("Manufacturer: " + manufacturer);
Console.WriteLine("Model: " + item["Model"].ToString());
if ((manufacturer == "microsoft corporation" && item["Model"].ToString().ToUpperInvariant().Contains("VIRTUAL"))
|| manufacturer.Contains("vmware")
|| item["Model"].ToString() == "VirtualBox")
{
Console.WriteLine("Manufacturer: " + manufacturer);
Console.WriteLine("Model: " + item["Model"].ToString());
}
}
}
}
Console.Read();
But in addition to that I have to know if it is a Parallel or VnWare. Any idea?
Thank you
Base on the answer provided Final code will be like that, it works for me ok:
using (var searcher = new ManagementObjectSearcher("Select * from Win32_ComputerSystem"))
{
//TODO: Have to verify the difference between Parallel and Vm
using (var items = searcher.Get())
{
foreach (var item in items)
{
string manufacturer = item["Manufacturer"].ToString().ToLower();
string model = item["Model"].ToString().ToLower();
if (manufacturer.Contains("parallels") && model.Contains("parallels"))
{
Console.WriteLine("Parallels Detected");
}
else if(manufacturer.Contains("vmware"))
{
Console.WriteLine("VMWARE Detected");
}
}
}
}
Running this on VMware Fusion yields:
Manufacturer: vmware, inc.
Model: VMware Virtual Platform
Based on this, it looks like you would be safe checking whether Manufacturer or Model contains the string "parallels" to determine if it's running on parallels.

How to get HDD serial number

May be the title is duplicate. I am getting HDD of the laptop serial number successfully when no USB devices are connected. But when any USB is connected, the code gets the serial number of connected device. I only want the serial number of HDD of laptop or desktop even though USBs are connected.
Below is the code.
using System.Management;
namespace SystemInfo
{
public class Info1
{
public static String GetHDDSerialNo()
{
ManagementClass mangnmt = new ManagementClass("Win32_LogicalDisk");
ManagementObjectCollection mcol = mangnmt.GetInstances();
string result = "";
foreach (ManagementObject strt in mcol)
{
result += Convert.ToString(strt["VolumeSerialNumber"]);
}
return result;
}
}
}
try this
ManagementObjectSearcher theSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive WHERE InterfaceType='USB'");
foreach (ManagementObject currentObject in theSearcher.Get())
{
ManagementObject theSerialNumberObjectQuery = new ManagementObject("Win32_PhysicalMedia.Tag='" + currentObject["DeviceID"] + "'");
MessageBox.Show(theSerialNumberObjectQuery["SerialNumber"].ToString());
}
You can use WMI Win32_DiskDrive, filter on MediaType containing "fixed" and get the SerialNumber
Something like :
public static String GetHDDSerialNo()
{
ManagementClass mangnmt = new ManagementClass("Win32_DiskDrive");
ManagementObjectCollection mcol = mangnmt.GetInstances();
string result = "";
foreach (ManagementObject strt in mcol)
{
if (Convert.ToString(strt["MediaType"]).ToUpper().Contains("FIXED"))
{
result += Convert.ToString(strt["SerialNumber"]);
}
}
return result;
}
Media type can contain "External", "Removable", "fixed". Exact string depends on OS. On Seven and XP, that String can be different. That's why we use Contains.
little reading

WMI reports non-unique processor id, hard drive id and mac address

We have the following code to retrive the harddrive id processor id and mac address:
private static string GetWMIValue(string query, string propertyName)
{
try
{
using (ManagementObjectSearcher search = new ManagementObjectSearcher(query))
{
using (ManagementObjectCollection results = search.Get())
{
foreach (var result in results)
{
if (result != null && result[propertyName] != null)
{
return result[propertyName].ToString();
}
}
}
}
}
catch
{
// do nothing.
}
return null;
}
public static string GetHardDriveSerialNumber()
{
string driveLetterName = Assembly.GetExecutingAssembly().Location.Substring(0, 1);
return GetWMIValue("SELECT VolumeSerialNumber FROM Win32_LogicalDisk WHERE DeviceID=\"" + driveLetterName + ":\"", "VolumeSerialNumber");
}
public static string GetProcessorId()
{
return GetWMIValue("SELECT ProcessorId FROM Win32_Processor", "ProcessorId");
}
public static string GetMacAddress()
{
return GetWMIValue("SELECT MacAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = TRUE", "MacAddress");
}
This works fine EXCEPT on one particular brand of tablet (as far as we know). On this brand, every machine has the same 3 values. As you can imagine, this screws with our licensing somewhat.
Has anyone ever seen this or does anyone have a better more reliable mechanism?
Thanks
After a bit more research it appears as though this is a well know and talked about issue. I need to do some work...
Thankfully, this wasn't my mess :) I just have to fix it :(

Categories