Get USB Drive Serial Number on Windows in C++
Getting the serial number of a USB device in Windows is a lot harder than it should be. ( But it’s a lot easier than getting the USB serial number on Os X!)
It is relatively simple to get USB information if you have the device handle of the USB device itself. And you can get information about a mounted volume pretty easily. But to match up a mounted volume with a USB device is tricky and annoying.
There are many code examples on the net about how to do it in C#, visual basic, and similar broken garbage languages. If you are forced to program in C# or visual basic – get help. There are alternatives to suicide. There are some example of how to do it through WMI, which is a Windows operating system service – which is slow, buggy and unreliable. If you have to do it in WMI, you are beyond help.
First here is the code:
#include <WinIOCtl.h> #include <api/usbioctl.h> #include <Setupapi.h> DEFINE_GUID( GUID_DEVINTERFACE_USB_DISK, 0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b ); void getDeviceInfo( int vol ) { UsbDeviceInfo info; // get the device handle char devicePath[7] = "\\\\.\\@:"; devicePath[4] = (char)( vol + 'A' ); HANDLE deviceHandle = CreateFile( devicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); if ( deviceHandle == INVALID_HANDLE_VALUE ) return; // to get the device number DWORD volumeDeviceNumber = getDeviceNumber( deviceHandle ); CloseHandle( deviceHandle ); // Get device interface info set handle // for all devices attached to the system HDEVINFO hDevInfo = SetupDiGetClassDevs( &GUID_DEVINTERFACE_USB_DISK, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE ); if ( hDevInfo == INVALID_HANDLE_VALUE ) return; // Get a context structure for the device interface // of a device information set. BYTE Buf[1024]; PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf; SP_DEVICE_INTERFACE_DATA spdid; SP_DEVINFO_DATA spdd; spdid.cbSize = sizeof( spdid ); DWORD dwIndex = 0; while ( true ) { if ( ! SetupDiEnumDeviceInterfaces( hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DISK, dwIndex, &spdid )) break; DWORD dwSize = 0; SetupDiGetDeviceInterfaceDetail( hDevInfo, &spdid, NULL, 0, &dwSize, NULL ); if (( dwSize != 0 ) && ( dwSize <= sizeof( Buf ))) { pspdidd->cbSize = sizeof( *pspdidd ); // 5 Bytes! ZeroMemory((PVOID)&spdd, sizeof(spdd)); spdd.cbSize = sizeof(spdd); long res = SetupDiGetDeviceInterfaceDetail( hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd ); if ( res ) { HANDLE hDrive = CreateFile( pspdidd->DevicePath,0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); if ( hDrive != INVALID_HANDLE_VALUE ) { DWORD usbDeviceNumber = getDeviceNumber( hDrive ); if ( usbDeviceNumber == volumeDeviceNumber ) { fprintf( "%s", pspdidd->DevicePath ); } } CloseHandle( hDrive ); } } dwIndex++; } SetupDiDestroyDeviceInfoList(hDevInfo); return; }
You pass in the volume number. This is just the drive letter, represented as an integer. Drive “A:” is zero. So the first thing we do is create a drive path. For example, if the mounted volume you want to get a serial number for is “F:”, you’d pass in “5”, and construct a device path \\.\F:
.
Next you get a device handle for that volume using CreateFile()
. Originally this function was meant to create regular file system files. But today it can be used to open handles to devices of all kinds. Each device type is represented by different device paths.
Next, you get the device number. When a volume is mounted, it will be associated with a device, and this function returns its number. Why the OS doesn’t just give you a device path here is ridiculous. The device numbers will be low. Typically a number under 10. Don’t be surprised. I was.
You get the device number by calling DeviceIOControl()
with the handle to your device:
DWORD getDeviceNumber( HANDLE deviceHandle ) { STORAGE_DEVICE_NUMBER sdn; sdn.DeviceNumber = -1; DWORD dwBytesReturned = 0; if ( !DeviceIoControl( deviceHandle, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof( sdn ), &dwBytesReturned, NULL ) ) { // handle error - like a bad handle. return U32_MAX; } return sdn.DeviceNumber; }
There is an old windows API, in setupaip.h
that was designed to help write installers. It has now been obsoleted by newer installation APIs, but you can still use it to enumerate devices. Basically you pass in the GUID of the type of device interface you want. In this case we just want to enumerate USB flash disks. That guid is defined at the top of the file.
You setup for enumeration with SetupDiGetClassDevs()
, and iterate over the devices with SetupDiEnumDeviceInterfaceDetail()
. When you are done you close the iterator with SetupDiDestroyDeviceInfoList()
.
Then for each device you get the device name and number using SetupDiGetDeviceInterfaceDetail
, and match up the device number of each device with the one you got for the volume. When you find a match then you have the device path for your actual USB flash drive. At that point you could start querying the device itself using functions like DeviceIoControl()
, but in this case the information we want is coded right into the device path
Here is a typical device path for a USB flash disk:
\\?\usbstor#disk&ven_cbm&prod_flash_disk&rev_5.00#31120000dc0ce201&0# {53f56307-b6bf-11d0-94f2-00a0c91efb8b}
The device path for a flash disk must start with “usbstor”. This first part of the path is name of the driver, which on windows is called “usbstor”. The vendor id, product id, product revision and serial number are highlighted in red.
The following regular expressions will extract this information from the device path:
ven_([^&#]+) // vendor id prod_([^&#]+) // product id rev_([^&#]+) // revision id &[^#]*#([^&#]+) // serial number
Next here is a method to recognize if a volume is removable media (e.g. like a usb or firewire disk):
bool isRemovableMedia( s32 vol ) { char rootPath[5] = "@:\\"; rootPath[0] = (char)( vol + 'A' ); char szDosDeviceName[MAX_PATH]; char dosDevicePath[3] = "@:"; // get the drive type UINT DriveType = GetDriveType( rootPath ); if ( DriveType != DRIVE_REMOVABLE ) return false; dosDevicePath[0] = (char)( vol + 'A' ); QueryDosDevice( dosDevicePath, szDosDeviceName, MAX_PATH ); if ( strstr( szDosDeviceName,"\\Floppy") != NULL ) { // its a floppy return false; } return true; }
Resources:
CreateFile function | MSDN Reference for CreateFile |
SetupDiEnumDeviceInterfaces function | MSDN Reference for SetupDiEnumDeviceInterfaces |
GetDriveType function | MSDN Reference for GetDriveType |
DeviceIoControl function | MSDN Refence for DeviceIoControl |
DeviceIoControl function | MSDN Refence for DeviceIoControl |
How to get the Serial Number from a USB disk | Serial Number code examples in WMI and Visual Basic |
Windows Driver Kit (WDK) | The windows device driver toolkit. Has headers you may need |
Flash Memory Toolkit | A program to view the serial number, for debugging |
DiskId32 | Includes a link to a code example that gets serial numbers from hard disks |
How to Prepare a USB Drive for Safe Removal | Shows a similar method for mapping volumes to USB Devices |
Good article, thanks.
Can you provide c++ code using regular expressions to extract the serial number?
I use my own regular expression class, and to work it requires a whole bunch of my supporting libraries, so it wouldn’t make sense to post it here.
I know that the STL now supports regular expressions. Try
#include <regexp>
, and using theregexp
class.There is a tutorial on it here:
http://www.codeguru.com/cpp/cpp/cpp_mfc/stl/article.php/c15339/A-TR1-Tutorial-Regular-Expressions.htm
What if the USB device is not a drive? What if it’s a mouse? Or is everything on USB handled as a “drive”? Without going into all the gory details, I have put some additional circuitry into an old USB mouse to monitor some external events by whether the computer detects the old mouse or not. Now, all I need is the software component, under Windows, that would monitor that old mouse. Of course, the thing is, I need to make sure the program monitors the CORRECT USB device. Another problem is that I am a Unix/Linux person (and could do this with a simple script under Linux), but this aspect of Windows is an alien environment for me. I have installed the free Visual C++ Express, and what you mentioned in your article sounds good, but I don’t know if I’ll be able to apply it to a mouse, instead of a drive.
What do you think?
Excellent post! How to go about getting the serial number of a fixed disk drive? Thanks.
Great post! One question- where is getDeviceNumber defined? I can’t get this to compile because of that function.
That is one of my own functions. I’ll update the article with the implementation for that. But its pretty simple you get it from a call to DeviceIOControl.
select DeviceID from Win32_USBHub where Name=’USB Mass Storage Device’
I found on my USB drives, this string includes a unique id for the drive.
how to include the three header files..
#include
#include
#include
i didn’t find any source for these three files..
please help me …
Do you mean the three headers from the first code listing? Like “WinIOCtl.h”? These are standard windows header files. You should have them if you install Microsoft’s c++ compiler (Visual Studio). It comes with the windows platform sdk. These should be included with that.
#include
comes from the Windows Driver Kit, not from Visual Studio.
Reshma should download the WDK from the link that you gave.
The other two are in the SDK.
Although your GUID is correct, you don’t have to do it.
DEFINE_GUID( GUID_DEVINTERFACE_USB_DISK,
0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2,
0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b );
Instead, you can get it from the WDK.
To use the WDK, this looks redundant but you have to do it this way.
#include “ap/\Ntddstor.h”
#include “initguid.h”
#include “api/Ntddstor.h”
UsbDeviceInfo info;
isn’t needed. In order to compile, just delete it ^_^
I will try to correct an editing error in my previous reply.
If the owner edits my previous reply and deletes this one, that will be good.
#include “api/Ntddstor.h”
#include “initguid.h”
#include “api/Ntddstor.h”
(The redundancy is that Ntddstor.h has to be included twice. The pathname shouldn’t be messed up like I did.)
Sorry, I have to edit this again.
After this:
#include “api/Ntddstor.h”
#include “initguid.h”
#include “api/Ntddstor.h”
Use
GUID_DEVINTERFACE_DISK
instead of
GUID_DEVINTERFACE_USB_DISK
In the device info in your example, Windows has set the device’s serial number to
31120000dc0ce201
I bet the device’s actual serial number might be
31120000DC0CE201
Windows’s case insensivity is OK for Windows, but it might not be OK for some application that needs to know the real serial number.
Actually Windows did more than just be case insensitive. In filenames, Windows is case insensitive in matching names, but when writing names in the first place it preserves the case that the user used when creating the file. If you look in Windows Explorer or a dir command, You CAN Still See Names like tHis. But Windows gives us the device’s serial number as if it were all lower case.
Does anyone know how to find the real serial number?
Can this code work in Windows 8.1 Metro Style app?
Could not get this to work, do you have the source code? I get a lnk2019 error maybe i falied to add some libs?
I was trying the same on python,. when i saw your post, i tried to compile it (I am new to all programming, i installed mingw ) with this command
g++ findserial.cpp -o findserial.out
i get this following error
findserial.cpp:2:26: fatal error: api/usbioctl.h: No such file or directory
compilation terminated.
can anyone please help
In case you need the serialnumber to be case sensitive, you can utilize the following code to extract it from the registry.
The parameter usbDeviceId is expected to be in the format
“USB\\VID_XXXX&PID_XXXX” and can be obtained using CM_Get_Device_ID.
Although my example uses Qt it might help:
QString ExtractSerialNumberFromRegistry(const QString& usbDeviceId)
{
QString serial;
QStringList parts = usbDeviceId.split(“\\”);
if (parts.size() == 3)
{
QString parentNode(“SYSTEM\\CurrentControlSet\\Enum\\%1\\%2”);
HKEY hKey;
long result = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
reinterpret_cast(parentNode.arg(parts.at(0)).arg(parts.at(1)).utf16()),
0,
KEY_READ,
&hKey);
if (result == ERROR_SUCCESS)
{
wchar_t* subkey = new wchar_t[255];
DWORD subkey_length = 255;
DWORD counter = 0;
while (result != ERROR_NO_MORE_ITEMS)
{
result = RegEnumKeyEx(hKey, counter, subkey, &subkey_length, 0, NULL, NULL, NULL);
if (result == ERROR_SUCCESS)
{
// find best match using the uppercase serial nested in the usb device id
const QString currSerial(reinterpret_cast(subkey));
if (result == ERROR_SUCCESS && parts.last().toUpper() == currSerial.toUpper())
serial = currSerial;
}
++counter;
}
delete subkey;
subkey = 0;
RegCloseKey(hKey);
}
}
return serial;
}
By the way, removing the “api/usbioctl.h” include and the line with “UsbDeviceInfo” seems to work fine also. That looks like a spurious dependency.