Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Get HDD Serial Number with C#

0.00/5 (No votes)
10 Jun 2018 1  
How to obtain HDD information with C#

HDD info

Introduction

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.)

Background

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,
                                           IntPtr.Zero,
                                           Native.OPEN_EXISTING,
                                           Native.FILE_ATTRIBUTE_READONLY,
                                           IntPtr.Zero);

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
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
    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
        );
//for IOCTL_STORAGE_QUERY_PROPERTY
[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];
            }
        }

[StructLayout(LayoutKind.Sequential)]
        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];
            }
        }  
	[StructLayout(LayoutKind.Sequential)]
        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 = " ";
 Native.SENDCMDINPARAMS sci = new Native.SENDCMDINPARAMS();
 Native.SENDCMDOUTPARAMS sco = new Native.SENDCMDOUTPARAMS();  
     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;
else
     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.

    [StructLayout(LayoutKind.Sequential)]
    public struct STORAGE_PROPERTY_QUERY
    {
        public uint PropertyId;
        public uint QueryType;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public byte[] AdditionalParameters;
    }
    [StructLayout(LayoutKind.Sequential)]
    struct STORAGE_DESCRIPTOR_HEADER
    {
        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
    }
[StructLayout(LayoutKind.Sequential)]
    public struct STORAGE_DEVICE_DESCRIPTOR
    {
        public uint Version;
        public uint Size;
        public byte DeviceType;
        public byte DeviceTypeModifier;
        [MarshalAs(UnmanagedType.U1)]
        public bool RemovableMedia;
        [MarshalAs(UnmanagedType.U1)]
        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 = "";
  		 STORAGE_PROPERTY_QUERY query = new STORAGE_PROPERTY_QUERY();
                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, 
						IntPtr.Zero);
	if(headerBufferSize==headerBytesReturned && result==true)
                {
                    STORAGE_DESCRIPTOR_HEADER header = (STORAGE_DESCRIPTOR_HEADER)
                    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, 
						IntPtr.Zero);
  		  if(result)
                    {
                        STORAGE_DEVICE_DESCRIPTOR descriptor = (STORAGE_DEVICE_DESCRIPTOR)
			Marshal.PtrToStructure
                             (descriptorBufferPointer, typeof(STORAGE_DEVICE_DESCRIPTOR));
                        byte[] descriptorBuffer = new byte[descriptorBufferSize];
                        Marshal.Copy(descriptorBufferPointer, 
                             descriptorBuffer, 0, descriptorBuffer.Length);
                        vendorId = GetData(descriptorBuffer, (int)descriptor.VendorIdOffset);
                        productId = GetData(descriptorBuffer, (int)descriptor.ProductIdOffset);
                        productRevision = GetData(descriptorBuffer, 
                                          (int)descriptor.ProductRevisionOffset);
                        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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here