Click here to Skip to main content
16,004,453 members
Articles / Programming Languages / C#

Get Physical HDD Serial Number without WMI

Rate me:
Please Sign up or sign in to vote.
4.79/5 (63 votes)
6 May 2008CPOL2 min read 983.1K   62.9K   178   196
Retrieve the physical Hard drive ID and other info using low level APIs like DeviceIOControl

Sample Image - DriveInfo.png

Introduction

Many people looking for a schema to protect their work need to get some information that is hardware specific like the Mac Address or some hard drive serial number.

Background

If you tried other solutions like like this one, it probably did not work for you because it's using the WMI services. I was able to find a solution that worked reasonably well here. It made low level calls to the disk using commands sent by the DeviceIoControl API. The code was not very reusable unless you used native C++. Therefore I brushed it a bit and made it look more Object Oriented. Most importantly, I exposed the drive information through a .NET collection.

Using the Code

Since the collection is written in MC++, I've included some Microsoft DLLs from the redistributable pack in the demo zip. Also it's mandatory to use .NET 2.0 since the collection is generic.

The code is very easy to use from any .NET language, like C# for instance:

C#
m_list = new DriveListEx();
m_list.Load();
//bind to a a grid view
m_dataGridView.DataSource = m_list;

Points of Interest

The information about the internal drives is gathered in DiskInfo::LoadDiskInfo();

DiskInfo is a native singleton class that wraps the calls to ReadPhysicalDriveInNTWithAdminRights() and ReadIdeDriveAsScsiDriveInNT(). I ignored the ReadPhysicalDriveInNTWithZeroRights() that did not seem to work anyways.

Both functions will call AddIfNew() if they can retrieve the information.

Internally there is a list that holds the raw information about the drives and that is updated when a new drive information was found.

C++
BOOL DiskInfo::AddIfNew(USHORT *pIdSector)
{
  BOOL bAdd = TRUE;
  for(UINT i =0; i< m_list.size();i++)
  {
    if(memcmp(pIdSector,m_list[i],256 * sizeof(WORD)) == 0)
    {
       bAdd = false;
       break;
    }
  }
   if(bAdd)
   {
      WORD* diskdata = new WORD[256];
      ::memcpy(diskdata,pIdSector,256*sizeof(WORD));
      m_list.push_back(diskdata);
   }
  return bAdd;
}

If you are stuck with a non .NET compiler, you could still use the source code from UnmanagedCode.cpp, just uncomment the #define NATIVE_CODE line.
This build is for Windows XP 32 bit systems. If you need it for Vista or 64 bit systems, you should select the right include and lib folders when building and should not use the additional DLLs from the release.zip archive, since they are 32 bit for Windows XP.

History

  • Version 1.1: Added ReadPhysicalDriveInNTUsingSmart for reading the HDD info.
    P.S. I did not get a chance to test it for Windows 95 and alike.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Decebal Mihailescu is a software engineer with interest in .Net, C# and C++.

Comments and Discussions

 
AnswerRe: information of external hard drives ? Pin
dmihailescu11-Apr-10 16:42
dmihailescu11-Apr-10 16:42 
GeneralRe: information of external hard drives ? Pin
georec12-Apr-10 10:04
georec12-Apr-10 10:04 
GeneralNot Works on Vista or Win7 Pin
Hossam™ Ahmed26-Feb-10 14:50
Hossam™ Ahmed26-Feb-10 14:50 
GeneralRe: Not Works on Vista or Win7 Pin
autenje21-Jun-10 3:12
autenje21-Jun-10 3:12 
GeneralRe: Not Works on Vista or Win7 Pin
Hossam™ Ahmed27-Jun-10 22:02
Hossam™ Ahmed27-Jun-10 22:02 
GeneralRe: Not Works on Vista or Win7 Pin
minregol27-Jul-10 2:10
minregol27-Jul-10 2:10 
GeneralVS2008 building errors Pin
jp chow22-Feb-10 14:43
jp chow22-Feb-10 14:43 
GeneralRe: VS2008 building errors Pin
miliu9-May-10 15:28
miliu9-May-10 15:28 
I ran into the same errors, but there is an easy fix. The problem is that some of the structures have already been defined in VS2008 (winioctl.h). You just need to remove or comment out those structures in UnmanagedCode.h. This is how my UnmanagedCode.h looks like afterwards:

#pragma once


#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <windows.h>
#include <winioctl.h>

#pragma warning(disable:4996)
   //  Required to ensure correct PhysicalDrive IOCTL structure setup
#pragma pack(1)

#define  IDENTIFY_BUFFER_SIZE  512


   //  IOCTL commands
#define  DFP_GET_VERSION          0x00074080
#define  DFP_SEND_DRIVE_COMMAND   0x0007c084
#define  DFP_RECEIVE_DRIVE_DATA   0x0007c088

#define  FILE_DEVICE_SCSI              0x0000001b
#define  IOCTL_SCSI_MINIPORT_IDENTIFY  ((FILE_DEVICE_SCSI << 16) + 0x0501)
#define  IOCTL_SCSI_MINIPORT 0x0004D008  //  see NTDDSCSI.H for definition


#define SMART_GET_VERSION               CTL_CODE(IOCTL_DISK_BASE, 0x0020, METHOD_BUFFERED, FILE_READ_ACCESS)
#define SMART_SEND_DRIVE_COMMAND        CTL_CODE(IOCTL_DISK_BASE, 0x0021, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define SMART_RCV_DRIVE_DATA            CTL_CODE(IOCTL_DISK_BASE, 0x0022, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)

   //  GETVERSIONOUTPARAMS contains the data returned from the 
   //  Get Driver Version function.
typedef struct _GETVERSIONOUTPARAMS
{
   BYTE bVersion;      // Binary driver version.
   BYTE bRevision;     // Binary driver revision.
   BYTE bReserved;     // Not used.
   BYTE bIDEDeviceMap; // Bit map of IDE devices.
   DWORD fCapabilities; // Bit mask of driver capabilities.
   DWORD dwReserved[4]; // For future use.
} GETVERSIONOUTPARAMS, *PGETVERSIONOUTPARAMS, *LPGETVERSIONOUTPARAMS;


   //  Bits returned in the fCapabilities member of GETVERSIONOUTPARAMS 
#define  CAP_IDE_ID_FUNCTION             1  // ATA ID command supported
#define  CAP_IDE_ATAPI_ID                2  // ATAPI ID command supported
#define  CAP_IDE_EXECUTE_SMART_FUNCTION  4  // SMART commannds supported

   //  Valid values for the bCommandReg member of IDEREGS.
#define  IDE_ATAPI_IDENTIFY  0xA1  //  Returns ID sector for ATAPI.
#define  IDE_ATA_IDENTIFY    0xEC  //  Returns ID sector for ATA.

   // The following struct defines the interesting part of the IDENTIFY
   // buffer:
typedef struct _IDSECTOR
{
   USHORT  wGenConfig;
   USHORT  wNumCyls;
   USHORT  wReserved;
   USHORT  wNumHeads;
   USHORT  wBytesPerTrack;
   USHORT  wBytesPerSector;
   USHORT  wSectorsPerTrack;
   USHORT  wVendorUnique[3];
   CHAR    sSerialNumber[20];
   USHORT  wBufferType;
   USHORT  wBufferSize;
   USHORT  wECCSize;
   CHAR    sFirmwareRev[8];
   CHAR    sModelNumber[40];
   USHORT  wMoreVendorUnique;
   USHORT  wDoubleWordIO;
   USHORT  wCapabilities;
   USHORT  wReserved1;
   USHORT  wPIOTiming;
   USHORT  wDMATiming;
   USHORT  wBS;
   USHORT  wNumCurrentCyls;
   USHORT  wNumCurrentHeads;
   USHORT  wNumCurrentSectorsPerTrack;
   ULONG   ulCurrentSectorCapacity;
   USHORT  wMultSectorStuff;
   ULONG   ulTotalAddressableSectors;
   USHORT  wSingleWordDMA;
   USHORT  wMultiWordDMA;
   BYTE    bReserved[128];
} IDSECTOR, *PIDSECTOR;

//
// IDENTIFY data (from ATAPI driver source)
//

#pragma pack(1)

typedef struct _IDENTIFY_DATA {
    USHORT GeneralConfiguration;            // 00 00
    USHORT NumberOfCylinders;               // 02  1
    USHORT Reserved1;                       // 04  2
    USHORT NumberOfHeads;                   // 06  3
    USHORT UnformattedBytesPerTrack;        // 08  4
    USHORT UnformattedBytesPerSector;       // 0A  5
    USHORT SectorsPerTrack;                 // 0C  6
    USHORT VendorUnique1[3];                // 0E  7-9
    USHORT SerialNumber[10];                // 14  10-19
    USHORT BufferType;                      // 28  20
    USHORT BufferSectorSize;                // 2A  21
    USHORT NumberOfEccBytes;                // 2C  22
    USHORT FirmwareRevision[4];             // 2E  23-26
    USHORT ModelNumber[20];                 // 36  27-46
    UCHAR  MaximumBlockTransfer;            // 5E  47
    UCHAR  VendorUnique2;                   // 5F
    USHORT DoubleWordIo;                    // 60  48
    USHORT Capabilities;                    // 62  49
    USHORT Reserved2;                       // 64  50
    UCHAR  VendorUnique3;                   // 66  51
    UCHAR  PioCycleTimingMode;              // 67
    UCHAR  VendorUnique4;                   // 68  52
    UCHAR  DmaCycleTimingMode;              // 69
    USHORT TranslationFieldsValid:1;        // 6A  53
    USHORT Reserved3:15;
    USHORT NumberOfCurrentCylinders;        // 6C  54
    USHORT NumberOfCurrentHeads;            // 6E  55
    USHORT CurrentSectorsPerTrack;          // 70  56
    ULONG  CurrentSectorCapacity;           // 72  57-58
    USHORT CurrentMultiSectorSetting;       //     59
    ULONG  UserAddressableSectors;          //     60-61
    USHORT SingleWordDMASupport : 8;        //     62
    USHORT SingleWordDMAActive : 8;
    USHORT MultiWordDMASupport : 8;         //     63
    USHORT MultiWordDMAActive : 8;
    USHORT AdvancedPIOModes : 8;            //     64
    USHORT Reserved4 : 8;
    USHORT MinimumMWXferCycleTime;          //     65
    USHORT RecommendedMWXferCycleTime;      //     66
    USHORT MinimumPIOCycleTime;             //     67
    USHORT MinimumPIOCycleTimeIORDY;        //     68
    USHORT Reserved5[2];                    //     69-70
    USHORT ReleaseTimeOverlapped;           //     71
    USHORT ReleaseTimeServiceCommand;       //     72
    USHORT MajorRevision;                   //     73
    USHORT MinorRevision;                   //     74
    USHORT Reserved6[50];                   //     75-126
    USHORT SpecialFunctionsEnabled;         //     127
    USHORT Reserved7[128];                  //     128-255
} IDENTIFY_DATA, *PIDENTIFY_DATA;

#pragma pack()

typedef struct _SRB_IO_CONTROL
{
   ULONG HeaderLength;
   UCHAR Signature[8];
   ULONG Timeout;
   ULONG ControlCode;
   ULONG ReturnCode;
   ULONG Length;
} SRB_IO_CONTROL, *PSRB_IO_CONTROL;
#define SMART_RCV_DRIVE_DATA            CTL_CODE(IOCTL_DISK_BASE, 0x0022, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#ifndef NATIVE_CODE
using namespace System;
#endif
#include <vector>
using namespace std ;
typedef vector<WORD*> LISTDATA;
#define  MAX_IDE_DRIVES  16
class DiskInfo
{
	static DiskInfo* m_instance;
	static char HardDriveSerialNumber [1024];
	LISTDATA m_list;
public:
	static DiskInfo& GetDiskInfo()
	{
		if(m_instance == NULL)
			m_instance =  new DiskInfo();
		return *m_instance;
	};

	static void Destroy()
	{
		if(m_instance != NULL)
			delete m_instance;
		m_instance = NULL;
	};
	~DiskInfo(void);
	int ReadIdeDriveAsScsiDriveInNT (void);
	long LoadDiskInfo ();
	int ReadPhysicalDriveInNTWithAdminRights (void);
	int ReadPhysicalDriveInNTWithZeroRights (void);
	int ReadDrivePortsInWin9X (void);
	int ReadPhysicalDriveInNTUsingSmart (void);

	unsigned __int64 DriveSize (UINT drive);
	UINT GetCount (){return m_list.size();};
	UINT BufferSize (UINT drive);
#ifndef NATIVE_CODE
	String^ GetModelNumber (UINT drive);
	String^ GetSerialNumber(UINT drive);
	String^ GetRevisionNumber (UINT drive);
	String^ GetDriveType (UINT drive);
#else
	char* ModelNumber (UINT drive);
	char* SerialNumber (UINT drive);
	char* RevisionNumber (UINT drive);
	char* DriveType (UINT drive);
#endif
private:
	DiskInfo(void);
	static char * flipAndCodeBytes (char * str);
	static char * ConvertToString (WORD diskdata [256], int firstIndex, int lastIndex);

	static BOOL DoIDENTIFY (HANDLE hPhysicalDriveIOCTL, PSENDCMDINPARAMS pSCIP,
                 PSENDCMDOUTPARAMS pSCOP, BYTE bIDCmd, BYTE bDriveNum,
                 PDWORD lpcbBytesReturned);
	BOOL AddIfNew(USHORT *pIdSector);
#ifndef NATIVE_CODE
	String^ GetString (WORD* pdiskdata, int firstIndex, int lastIndex);
#endif
};

GeneralGreat Example! Pin
Slowpokee52803-Dec-09 11:12
Slowpokee52803-Dec-09 11:12 
QuestionGreate Pin
Khaniya18-Nov-09 1:44
professionalKhaniya18-Nov-09 1:44 
AnswerRe: Greate Pin
dmihailescu18-Nov-09 12:52
dmihailescu18-Nov-09 12:52 
QuestionHDD manufacturer serial no without admin rights for web applications? Pin
christopherangler8-Oct-09 23:17
christopherangler8-Oct-09 23:17 
QuestionDriveInfoEx.dll supports Windows Server 2008? Pin
christopherangler7-Oct-09 17:14
christopherangler7-Oct-09 17:14 
AnswerRe: DriveInfoEx.dll supports Windows Server 2008? Pin
dmihailescu8-Oct-09 12:42
dmihailescu8-Oct-09 12:42 
QuestionDriveInfoEx.dll supports Microsoft Win 64 bit machines? Pin
christopherangler7-Oct-09 17:13
christopherangler7-Oct-09 17:13 
AnswerRe: DriveInfoEx.dll supports Microsoft Win 64 bit machines? Pin
dmihailescu8-Oct-09 12:44
dmihailescu8-Oct-09 12:44 
QuestionRe: DriveInfoEx.dll supports Microsoft Win 64 bit machines? Pin
razvar15-Oct-09 2:10
razvar15-Oct-09 2:10 
AnswerRe: DriveInfoEx.dll supports Microsoft Win 64 bit machines? Pin
dmihailescu15-Oct-09 12:52
dmihailescu15-Oct-09 12:52 
GeneralRe: DriveInfoEx.dll supports Microsoft Win 64 bit machines? Pin
razvar15-Oct-09 21:04
razvar15-Oct-09 21:04 
QuestionRe: DriveInfoEx.dll supports Microsoft Win 64 bit machines? Pin
Ankit___26-Apr-10 0:22
professionalAnkit___26-Apr-10 0:22 
AnswerRe: DriveInfoEx.dll supports Microsoft Win 64 bit machines? Pin
dmihailescu26-Apr-10 12:53
dmihailescu26-Apr-10 12:53 
QuestionInterface types compatibility with DriveInfoEx.dll Pin
christopherangler7-Oct-09 17:09
christopherangler7-Oct-09 17:09 
AnswerRe: Interface types compatibility with DriveInfoEx.dll Pin
dmihailescu8-Oct-09 12:45
dmihailescu8-Oct-09 12:45 
QuestionHard Disk Manufacturers compatibilty Pin
christopherangler7-Oct-09 17:05
christopherangler7-Oct-09 17:05 
AnswerRe: Hard Disk Manufacturers compatibilty Pin
dmihailescu8-Oct-09 12:48
dmihailescu8-Oct-09 12:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.