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.
- Wmi is very slow and doesn't work on mini windows XP platform.
- Third party SDK or libs are generally charged.
- 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.
- 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++)
{
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:
[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
);
[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;
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);
}
return Helper.SwapChars(str.ToCharArray());
}
Also GetData
function's definition is here:
public static string GetData(byte[] array, int index, bool reverse = false)
{
if (array== null || array.Length == 0 || index <= 0 || index >= array.Length) return "";
int i;
for (i = index; i < array.Length; i++)
{
if (array[i] == 0) break;
}
if (index == i) return "";
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.