Click here to Skip to main content
15,609,772 members
Articles / Programming Languages / C#
Posted 9 Jun 2018


18 bookmarked

Get HDD Serial Number with C#

Rate me:
Please Sign up or sign in to vote.
4.06/5 (11 votes)
10 Jun 2018CPOL2 min read
How to obtain HDD information with C#

HDD info


In this article, I will demonstrate how to obtain HDD information with C# by Win API (pinvoke).

HDD information include serial number, firmware, model name, drivetype (fixed, removable, etc.)


There are many ways to get HDD information in Windows such as win API, WMI, third party SDK, etc.

  1. Wmi is very slow and doesn't work on mini windows XP platform.
  2. Third party SDK or libs are generally charged.
  3. Native coding with C/C++, Delphi. I think, low level API programming is harder than normal programming because there is very detailed information you have to know.
  4. Win API wrapper for C# which is the path I am tending to (Pinvoke)

Using the Code

At first, we should define CreateFile function. Detailed information.

[DllImport("kernel32.dll", CharSet = CharSet.Auto,
    CallingConvention = CallingConvention.StdCall, SetLastError = true)]
      public static extern SafeFileHandle CreateFile(
       string lpFileName,
       uint dwDesiredAccess,
       uint dwShareMode,
       IntPtr SecurityAttributes,
       uint dwCreationDisposition,
       uint dwFlagsAndAttributes,
       IntPtr hTemplateFile);

This function is necessary to access the physical drive.

I used it like this:

SafeFileHandle hndl = NativeApi.CreateFile(volume,
                                           Native.GENERIC_READ | Native.GENERIC_WRITE,
                                           Native.FILE_SHARE_READ | Native.FILE_SHARE_WRITE,

This is our createfile function which is called.

We need to enumerate physical drives like this and we need to call "CreateFile" function for each one.

for (uint i = 0; i < MAX_NUMBER_OF_DRIVES; i++)
              // try to open the current physical drive
              string volume = string.Format("<a>\\\\.\\PhysicalDrive{0</a>}", i);

With this code, we are trying to open all mounted physical drives and now we get handles for each one.

Then, we can send IOCTL code to drive. I use deviceiocontrol function which is:

//  for Smart_IOCTL code
    public static extern int DeviceIoControl(
            SafeFileHandle hDevice,
            int dwIoControlCode,
            [In(), Out()] Native.SENDCMDINPARAMS lpInBuffer,
            int lpInBufferSize,
            [In(), Out()] Native.SENDCMDOUTPARAMS lpOutBuffer,
            int lpOutBufferSize,
            ref int lpBytesReturned,
            int lpOverlapped
[DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        SafeHandle hDevice,
        uint dwIoControlCode,
        IntPtr lpInBuffer,
        uint nInBufferSize,
        [Out] IntPtr lpOutBuffer,
        uint nOutBufferSize,
        ref uint lpBytesReturned,
        IntPtr lpOverlapped);

To retrieve information from HDD, we should send IOCTL code to disk drive.

I found three types of Win API IOCTL code to obtain HDD information which are IOCTL_SCSI_PASS_THROUGH, SMART_RCV_DRIVE_DATA, IOCTL_STORAGE_QUERY_PROPERTY.

First way is "SMART_RCV_DRIVE_DATA".

If you want to use SMART_RCV_DRIVE_DATA code, you have to ensure s.m.a.r.t. is enabled. Extra information.

If you want to use SMART_RCV_DRIVE_DATA code, we need to define these structs/classes. Here:

 [StructLayout(LayoutKind.Sequential, Size = 8)]
        public class IDEREGS
            public byte Features;
            public byte SectorCount;
            public byte SectorNumber;
            public byte CylinderLow;
            public byte CylinderHigh;
            public byte DriveHead;
            public byte Command;
            public byte Reserved;

  [StructLayout(LayoutKind.Sequential, Size = 32)]
        public class SENDCMDINPARAMS
            public int BufferSize;
            public IDEREGS DriveRegs;
            public byte DriveNumber;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
            public byte[] Reserved;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public int[] Reserved2;
            public SENDCMDINPARAMS()
                DriveRegs = new IDEREGS();
                Reserved = new byte[3];
                Reserved2 = new int[4];
  [StructLayout(LayoutKind.Sequential, Size = 12)]
        public class DRIVERSTATUS
            public byte DriveError;
            public byte IDEStatus;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public byte[] Reserved;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public int[] Reserved2;
            public DRIVERSTATUS()
                Reserved = new byte[2];
                Reserved2 = new int[2];

        public class IDSECTOR
            public short GenConfig;
            public short NumberCylinders;
            public short Reserved;
            public short NumberHeads;
            public short BytesPerTrack;
            public short BytesPerSector;
            public short SectorsPerTrack;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
            public short[] VendorUnique;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public char[] SerialNumber;
            public short BufferClass;
            public short BufferSize;
            public short ECCSize;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public char[] FirmwareRevision;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
            public char[] ModelNumber;
            public short MoreVendorUnique;
            public short DoubleWordIO;
            public short Capabilities;
            public short Reserved1;
            public short PIOTiming;
            public short DMATiming;
            public short BS;
            public short NumberCurrentCyls;
            public short NumberCurrentHeads;
            public short NumberCurrentSectorsPerTrack;
            public int CurrentSectorCapacity;
            public short MultipleSectorCapacity;
            public short MultipleSectorStuff;
            public int TotalAddressableSectors;
            public short SingleWordDMA;
            public short MultiWordDMA;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 382)]
            public byte[] Reserved2;
            public IDSECTOR()
                VendorUnique = new short[3];
                Reserved2 = new byte[382];
                FirmwareRevision = new char[8];
                SerialNumber = new char[20];
                ModelNumber = new char[40];
        public class SENDCMDOUTPARAMS
            public int BufferSize;
            public DRIVERSTATUS Status;
            public IDSECTOR IDS;
            public SENDCMDOUTPARAMS()
                Status = new DRIVERSTATUS();
                IDS = new IDSECTOR();

And now, we can send IOCTL code to HDD to retrieve information:

 int returnSize =0;
 string serialNumber = " ", model = " ", firmware = " ";
     sci.DriveNumber = (byte)driveNumber;
     sci.BufferSize = Marshal.SizeOf(sco);
     sci.DriveRegs.DriveHead = (byte)(0xA0 | driveNumber << 4);
     sci.DriveRegs.Command = 0xEC;
     sci.DriveRegs.SectorCount = 1;
     sci.DriveRegs.SectorNumber = 1;
 if (NativeApi.DeviceIoControl(hndl, Native.DFP_RECEIVE_DRIVE_DATA, sci, Marshal.SizeOf(sci), sco, 
				Marshal.SizeOf(sco), ref returnSize, 0) != 0)
         serialNumber = Helper.Swap(sco.IDS.SerialNumber);
         model = Helper.Swap(sco.IDS.ModelNumber);
         firmware = Helper.Swap(sco.IDS.FirmwareRevision);
if ((sco.IDS.GenConfig & 0x80) == 0x40)
     driveType = Native.MEDIA_TYPE.RemovableMedia;
else if ((sco.IDS.GenConfig & 0x40) == 0x40)
     driveType = Native.MEDIA_TYPE.FixedMedia;
     driveType = Native.MEDIA_TYPE.Unknown;

Also "Swap" helper function's definition is here:

public static string Swap(char[] array)
        for (int i = 0; i <= array.Length - 2; i += 2)
            char t;
            t = array[i];
            array[i] = array[i + 1];
            array[i + 1] = t;
        string s = new string(array);
        return s;

Now, we have got serialnumber, firmware, model name, drivetype.

Second way: After CreateFile function calling, we get handle and we can send ioctl code to retrieve information.

For this option, we need to define these structs and classes.

    public struct STORAGE_PROPERTY_QUERY
        public uint PropertyId;
        public uint QueryType;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public byte[] AdditionalParameters;
        public uint Version;
        public uint Size;
 public enum STORAGE_BUS_TYPE
        BusTypeUnknown = 0x00,
        BusTypeScsi = 0x1,
        BusTypeAtapi = 0x2,
        BusTypeAta = 0x3,
        BusType1394 = 0x4,
        BusTypeSsa = 0x5,
        BusTypeFibre = 0x6,
        BusTypeUsb = 0x7,
        BusTypeRAID = 0x8,
        BusTypeiScsi = 0x9,
        BusTypeSas = 0xA,
        BusTypeSata = 0xB,
        BusTypeSd = 0xC,
        BusTypeMmc = 0xD,
        BusTypeVirtual = 0xE,
        BusTypeFileBackedVirtual = 0xF,
        BusTypeMax = 0x10,
        BusTypeMaxReserved = 0x7F
        public uint Version;
        public uint Size;
        public byte DeviceType;
        public byte DeviceTypeModifier;
        public bool RemovableMedia;
        public bool CommandQueueing;
        public uint VendorIdOffset;
        public uint ProductIdOffset;
        public uint ProductRevisionOffset;
        public uint SerialNumberOffset;
        public STORAGE_BUS_TYPE BusType;
        public uint RawPropertiesLength;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x16)]
        public byte[] RawDeviceProperties;

Now sending IOCTL code:

string  vendorId = "";string productId = "";string productRevision = "";string serialNumber = "";
                query.PropertyId = 0;
                query.QueryType = 0;
                int inputBufferSize = Marshal.SizeOf(query.GetType());
                IntPtr inputBuffer = Marshal.AllocHGlobal(inputBufferSize);
                Marshal.StructureToPtr(query, inputBuffer, true);
                uint ioControlCode = Native.IOCTL_STORAGE_QUERY_PROPERTY;
                // Define and store output parameters.
                int headerBufferSize = Marshal.SizeOf(typeof(STORAGE_DESCRIPTOR_HEADER));
                IntPtr headerBuffer = Marshal.AllocHGlobal(headerBufferSize);
                uint headerBytesReturned = default(UInt32);
                bool result = NativeApi.DeviceIoControl(deviceHandle, ioControlCode, inputBuffer,
						 (uint)inputBufferSize, headerBuffer, 
						(uint)headerBufferSize, ref headerBytesReturned, 
	if(headerBufferSize==headerBytesReturned && result==true)
                    Marshal.PtrToStructure(headerBuffer, typeof(STORAGE_DESCRIPTOR_HEADER));
                    uint descriptorBufferSize = header.Size;
                    IntPtr descriptorBufferPointer = Marshal.AllocHGlobal((int)descriptorBufferSize);
                    uint descriptorBytesReturned = default(UInt32);
                    result = NativeApi.DeviceIoControl(deviceHandle, ioControlCode, inputBuffer,
						 (uint)inputBufferSize, descriptorBufferPointer, 
						descriptorBufferSize, ref descriptorBytesReturned, 
                             (descriptorBufferPointer, typeof(STORAGE_DEVICE_DESCRIPTOR));
                        byte[] descriptorBuffer = new byte[descriptorBufferSize];
                             descriptorBuffer, 0, descriptorBuffer.Length);
                        vendorId = GetData(descriptorBuffer, (int)descriptor.VendorIdOffset);
                        productId = GetData(descriptorBuffer, (int)descriptor.ProductIdOffset);
                        productRevision = GetData(descriptorBuffer, 
                        serialNumber = GetData(descriptorBuffer, (int)descriptor.SerialNumberOffset);
                        serialNumber = HexStringToBinary(serialNumber);

HexStringToBinary is decoder function for serialnumber info.

  public static string HexStringToBinary(string myHex)

            string str = "";
            for (int i = 0; i < myHex.Length; i += 2)
                str += (char)Int16.Parse(myHex.Substring(i, 2), NumberStyles.AllowHexSpecifier);

            // The serial number is encoded in HEX and with each two characters encoded swapped.
            // ER ABCD -> BADC -> '42414443'
            return Helper.SwapChars(str.ToCharArray());


Also GetData function's definition is here:

    public static string GetData(byte[] array, int index, bool reverse = false)
	//index is used to get data from particular position in the byte array
  	if (array== null || array.Length == 0 || index <= 0 || index >= array.Length) return "";
            int i;
            for (i = index; i < array.Length; i++)
        	//go until zero value in byte array which is like delimiter 
                if (array[i] == 0) break;
            if (index == i) return "";
 	//Now we need to create a buffer to split data buffer from main buffer	
            var valueBytes = new byte[i - index];
            Array.Copy(array, index, valueBytes, 0, valueBytes.Length);
            if (reverse) Array.Reverse(valueBytes);
            return System.Text.Encoding.ASCII.GetString(valueBytes).Trim();       

I didn't search any information about usage of IOCTL_SCSI_PASS_THROUGH so I didn't implement it yet. If you want further information, you can inspect this IOCTL code and its structs or classes. Method is generally similar. Send DeviceIoControl code and get information from HDD.

Points of Interest

Although there is considerable information on the internet, I pointed out all general approaches to get data from HDD. And I showed all different solutions which I could find. I shared this article because I thought it would be useful for someone. I hope this article helps you.


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

Written By
Software Developer (Senior) codepark software
Turkey Turkey
I m interested in C# ,Java,Delphi programming language.I developed many types program which are generally enterprise solution.I m also interested in security ,forensic,system software development,encryption,Windows services,network programming .I have a OOP and Desing Patterns book writing work.

Comments and Discussions

QuestionSource Code Pin
K3M4123-Jan-21 6:16
K3M4123-Jan-21 6:16 
QuestionSource Code Pin
Ahmad Zaib7-Oct-18 23:36
Ahmad Zaib7-Oct-18 23:36 
AnswerRe: Source Code Pin
hooger201711-Feb-19 6:59
hooger201711-Feb-19 6:59 
PraiseMy vote of 5! Pin
jediYL13-Jun-18 17:49
professionaljediYL13-Jun-18 17:49 
GeneralRe: My vote of 5! Pin
hasan bozkurt14-Jun-18 19:29
hasan bozkurt14-Jun-18 19:29 
GeneralMy vote of 1 Pin
Member 244330610-Jun-18 9:19
Member 244330610-Jun-18 9:19 
incoherent code fragments

Sorry but for me this is just throwing independent code fragemnts at me. OMG | :OMG:

- Picture is btw. unreadable.

Do you have a working sample?

- is this better and/or gives same results as e.g.:
Get Physical HDD Serial Number without WMI[^]
How to Retrieve the REAL Hard Drive Serial Number[^]

Code is difficult to analyze because:
- NativeApi ist that a class you used for the pinvokes?
- how do you define deviceHandle
- you use driveNumber without initalizing, how to set it's value
- and so on...
GeneralRe: My vote of 1 Pin
hasan bozkurt10-Jun-18 10:02
hasan bozkurt10-Jun-18 10:02 
GeneralRe: My vote of 1 Pin
Member 244330610-Jun-18 11:33
Member 244330610-Jun-18 11:33 
GeneralRe: My vote of 1 Pin
hasan bozkurt10-Jun-18 13:28
hasan bozkurt10-Jun-18 13:28 

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.