One frequently asked question is “how to (programmatically) get the serial number of a physical drive?” or “how to find my hard disk serial number?“.

One first simple attempt may be to call GetVolumeInformation. However, this function retrieves a serial number which is assigned by the operating system to a volume when it is formatted. It’s not what we want.

To get the serial number assigned to the hard disk (or another type of physical drive) by the manufacturer, we have to find other ways, like for example calling DeviceIoControl function or using Win32_PhysicalMedia WMI class.

Let’s begin with the first one.


Get serial number by using DeviceIoControl


To get the serial number of a physical drive, we can call DeviceIoControl with IOCTL_STORAGE_QUERY_PROPERTY control code.

Just follow these steps:


1.Call CreateFile function to get a handle to physical drive. First argument (lpFileName) may be \\.\PhysicalDrive0, \\.\PhysicalDrive1,\\.\PhysicalDrive2… for drive #0, #1, #2, and so on.


    // Format physical drive path (may be '\\.\PhysicalDrive0', '\\.\PhysicalDrive1' and so on).

    CString strDrivePath;

    strDrivePath.Format(_T("\\\\.\\PhysicalDrive%u"), nDriveNumber);

 

    // Get a handle to physical drive

    HANDLE hDevice = ::CreateFile(strDrivePath, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

2.Set the STORAGE_PROPERTY_QUERY input data structure.


    // Set the input data structure

    STORAGE_PROPERTY_QUERY storagePropertyQuery;

    ZeroMemory(&storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY));

    storagePropertyQuery.PropertyId = StorageDeviceProperty;

    storagePropertyQuery.QueryType = PropertyStandardQuery;

3.Call DeviceIoControl once for retrieving necessary size, then allocate the output buffer.


    // Get the necessary output buffer size

    STORAGE_DESCRIPTOR_HEADER storageDescriptorHeader = {0};

    DWORD dwBytesReturned = 0;

    if(! ::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,

        &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),

        &storageDescriptorHeader, sizeof(STORAGE_DESCRIPTOR_HEADER),

        &dwBytesReturned, NULL))

    {

        // handle error, do cleanup and return

    }

 

    // Alloc the output buffer

    const DWORD dwOutBufferSize = storageDescriptorHeader.Size;

    BYTE* pOutBuffer = new BYTE[dwOutBufferSize];

    ZeroMemory(pOutBuffer, dwOutBufferSize);

4.Call DeviceIoControl twice to get the storage device descriptor. The output buffer points to a STORAGE_DEVICE_DESCRIPTOR structure, followed by additional info like vendor ID, product ID, serial number, and so on. The serial number is a null-terminated ASCII string located atSerialNumberOffset bytes counted form the beginning of the output buffer.


    // Get the storage device descriptor

    if(! ::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,

            &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),

            pOutBuffer, dwOutBufferSize,

            &dwBytesReturned, NULL))

    {

        // handle error, do cleanup and return

    }

 

    // Now, the output buffer points to a STORAGE_DEVICE_DESCRIPTOR structure

    // followed by additional info like vendor ID, product ID, serial number, and so on.

    STORAGE_DEVICE_DESCRIPTOR* pDeviceDescriptor = (STORAGE_DEVICE_DESCRIPTOR*)pOutBuffer;

    const DWORD dwSerialNumberOffset = pDeviceDescriptor->SerialNumberOffset;

    if(dwSerialNumberOffset != 0)

    {

        // Finally, get the serial number

        strSerialNumber = CString(pOutBuffer + dwSerialNumberOffset);

    }

Now let’s put all together in an MFC sample application which gets then displays the serial number for physical drive #0.


DWORD GetPhysicalDriveSerialNumber(UINT nDriveNumber IN, CString& strSerialNumber OUT)

{

    DWORD dwRet = NO_ERROR;

    strSerialNumber.Empty();

 

    // Format physical drive path (may be '\\.\PhysicalDrive0', '\\.\PhysicalDrive1' and so on).

    CString strDrivePath;

    strDrivePath.Format(_T("\\\\.\\PhysicalDrive%u"), nDriveNumber);

 

    // Get a handle to physical drive

    HANDLE hDevice = ::CreateFile(strDrivePath, 0, FILE_SHARE_READ|FILE_SHARE_WRITE,  NULL, OPEN_EXISTING, 0, NULL);

 

    if(INVALID_HANDLE_VALUE == hDevice)

        return ::GetLastError();

 

    // Set the input data structure

    STORAGE_PROPERTY_QUERY storagePropertyQuery;

    ZeroMemory(&storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY));

    storagePropertyQuery.PropertyId = StorageDeviceProperty;

    storagePropertyQuery.QueryType = PropertyStandardQuery;

 

    // Get the necessary output buffer size

    STORAGE_DESCRIPTOR_HEADER storageDescriptorHeader = {0};

    DWORD dwBytesReturned = 0;

    if(! ::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,

        &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),

        &storageDescriptorHeader, sizeof(STORAGE_DESCRIPTOR_HEADER),

        &dwBytesReturned, NULL))

    {

        dwRet = ::GetLastError();

        ::CloseHandle(hDevice);

        return dwRet;

    }

 

    // Alloc the output buffer

    const DWORD dwOutBufferSize = storageDescriptorHeader.Size;

    BYTE* pOutBuffer = new BYTE[dwOutBufferSize];

    ZeroMemory(pOutBuffer, dwOutBufferSize);

 

    // Get the storage device descriptor

    if(! ::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,

            &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),

            pOutBuffer, dwOutBufferSize,

            &dwBytesReturned, NULL))

    {

        dwRet = ::GetLastError();

        delete []pOutBuffer;

        ::CloseHandle(hDevice);

        return dwRet;

    }

 

    // Now, the output buffer points to a STORAGE_DEVICE_DESCRIPTOR structure

    // followed by additional info like vendor ID, product ID, serial number, and so on.

    STORAGE_DEVICE_DESCRIPTOR* pDeviceDescriptor = (STORAGE_DEVICE_DESCRIPTOR*)pOutBuffer;

    const DWORD dwSerialNumberOffset = pDeviceDescriptor->SerialNumberOffset;

    if(dwSerialNumberOffset != 0)

    {

        // Finally, get the serial number

        strSerialNumber = CString(pOutBuffer + dwSerialNumberOffset);

    }

 

    // Do cleanup and return

    delete []pOutBuffer;

    ::CloseHandle(hDevice);

    return dwRet;

}


void CPhysDrivesDialog::OnButtonGetDriveSerialNumber()

{

    UpdateData();

 

    DWORD dwRet = GetPhysicalDriveSerialNumber(m_nDriveNumber, m_strSerialNumber);

    if(NO_ERROR != dwRet)

    {

        CString strError;

        strError.Format(_T("GetPhysicalDriveSerialNumber failed. Error: %u"), dwRet);

        AfxMessageBox(strError);

    }

 

    UpdateData(FALSE);

}

Notes


The above example intentionally shows a “flat” global function, just for learning purpose. Of course, each one may improve it by using a more object-oriented approach.

May notice that STORAGE_DEVICE_DESCRIPTOR gets more info than serial number (device type, vendor ID, product ID, and so on). This is a subject of improving, as well.

Next article will describe how to get serial number using WMI (Windows Management Instrumentation).