Thursday, December 8, 2011

kernel32.dll DeviceIoControl example in C#

Had to write some code to retrieve the partition alignment of some VM disks hosted on a NAS from within the client OS. Simple method using WMI is below, but that was a bit dull as WMI is too easy so I thought I'd create a native method using API calls to kernel32.dll and use DeviceIOControl... what a pain but below is a native method that shows how to get the native code working under C#...

WMI method - simples...
        void WMIMethod()         
  {
              string sComputer = "."; 
             ManagementScope scope = new ManagementScope("\\\\" + sComputer + "\\root\\cimv2");
             //"\\\\" + args[0] + "\\root\\cimv2");            
  scope.Connect();
             ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_DiskPartition");
             ManagementObjectSearcher searcher =  new ManagementObjectSearcher(scope, query);
               ManagementObjectCollection queryCollection = searcher.Get();
               // Console.WriteLine("MachineName={0}", args[0]);
             foreach (ManagementObject m in queryCollection)
             {                 
 // Display the remote computer information                 
  Console.Write("Disk={0}", m["DiskIndex"]);                 
  Console.Write(",Partition={0}", m["Index"]);                 
  foreach (ManagementBaseObject c in m.GetRelated("Win32_LogicalDisk"))     
             {                     
   Console.Write(",Drive={0}", c["name"]);                 
   }                 
  Console.Write(",StartingOffset={0}", m["StartingOffset"]);
                 Int64 i = Convert.ToInt64(m["StartingOffset"]) + Convert.ToInt64(m["Size"]);
                 Console.Write(",NextAlignedOffset={0}", i);
                 Console.WriteLine(",Size={0}", m["Size"]);
             }
           }

Now the tricky one using the native calls... Converted this to C# from a post on XtremeVB, works like a charm...

//using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security;

static class IOCtl
{

private const int GENERIC_READ = unchecked((int)0x80000000);
private const int FILE_SHARE_READ = 1;
private const int FILE_SHARE_WRITE = 2;
private const int OPEN_EXISTING = 3;
private const int IOCTL_DISK_GET_DRIVE_LAYOUT_EX = unchecked((int)0x00070050);
private const int ERROR_INSUFFICIENT_BUFFER = 122;

private enum PARTITION_STYLE : int
{
MBR = 0,
GPT = 1,
RAW = 2
}

private enum Partition : byte
{
ENTRY_UNUSED = 0,
FAT_12 = 1,
XENIX_1 = 2,
XENIX_2 = 3,
FAT_16 = 4,
EXTENDED = 5,
HUGE = 6,
IFS = 7,
OS2BOOTMGR = 0xa,
FAT32 = 0xb,
FAT32_XINT13 = 0xc,
XINT13 = 0xe,
XINT13_EXTENDED = 0xf,
PREP = 0x41,
LDM = 0x42,
UNIX = 0x63
}

[SuppressUnmanagedCodeSecurity()]
private class NativeMethods
{

[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern SafeFileHandle CreateFile(
string fileName,
int desiredAccess,
int shareMode,
IntPtr securityAttributes,
int creationDisposition,
int flagsAndAttributes,
IntPtr hTemplateFile);

[DllImport("kernel32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeviceIoControl(
SafeFileHandle hVol,
int controlCode,
IntPtr inBuffer,
int inBufferSize,
IntPtr outBuffer,
int outBufferSize,
ref int bytesReturned,
IntPtr overlapped);

}

// Needs to be explicit to do the union.
[StructLayout(LayoutKind.Explicit)]
private struct DRIVE_LAYOUT_INFORMATION_EX
{
[FieldOffset(0)]
public PARTITION_STYLE PartitionStyle;
[FieldOffset(4)]
public int PartitionCount;
[FieldOffset(8)]
public DRIVE_LAYOUT_INFORMATION_MBR Mbr;
[FieldOffset(8)]
public DRIVE_LAYOUT_INFORMATION_GPT Gpt;
// Forget partition entry, we can't marshal it directly
// as we don't know how big it is.
}

private struct DRIVE_LAYOUT_INFORMATION_MBR
{
public uint Signature;
}

// Sequential ensures the fields are laid out in memory
// in the same order as we write them here. Without it,
// the runtime can arrange them however it likes, and the
// type may no longer be blittable to the C type.
[StructLayout(LayoutKind.Sequential)]
private struct DRIVE_LAYOUT_INFORMATION_GPT
{
public Guid DiskId;
// C LARGE_INTEGER is 64 bit
public long StartingUsableOffset;
public long UsableLength;
// C ULONG is 32 bit
public int MaxPartitionCount;
}

[StructLayout(LayoutKind.Sequential)]
private struct PARTITION_INFORMATION_MBR
{
public byte PartitionType;
[MarshalAs(UnmanagedType.U1)]
public bool BootIndicator;
[MarshalAs(UnmanagedType.U1)]
public bool RecognizedPartition;
public UInt32 HiddenSectors;

// helper method - is the hi bit valid - if so IsNTFT has meaning.
public bool IsValidNTFT()
{
return (PartitionType & 0xc0) == 0xc0;
}

// is this NTFT - i.e. an NTFT raid or mirror.
public bool IsNTFT()
{
return (PartitionType & 0x80) == 0x80;
}

// the actual partition type.
public Partition GetPartition()
{
const byte mask = 0x3f;
return (Partition)(PartitionType & mask);
}

}

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
private struct PARTITION_INFORMATION_GPT
{
// Strangely, this works as sequential if you build x86,
// But for x64 you must use explicit.
[FieldOffset(0)]
public Guid PartitionType;
[FieldOffset(16)]
public Guid PartitionId;
[FieldOffset(32)]
//DWord64
public ulong Attributes;
[FieldOffset(40)]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 36)]
public string Name;
}

[StructLayout(LayoutKind.Explicit)]
private struct PARTITION_INFORMATION_EX
{
[FieldOffset(0)]
public PARTITION_STYLE PartitionStyle;
[FieldOffset(8)]
public long StartingOffset;
[FieldOffset(16)]
public long PartitionLength;
[FieldOffset(24)]
public int PartitionNumber;
[FieldOffset(28)]
[MarshalAs(UnmanagedType.U1)]
public bool RewritePartition;
[FieldOffset(32)]
public PARTITION_INFORMATION_MBR Mbr;
[FieldOffset(32)]
public PARTITION_INFORMATION_GPT Gpt;
}

public static void Main()
{
SendIoCtlDiskGetDriveLayoutEx(0);
Console.ReadKey();
}

private static void SendIoCtlDiskGetDriveLayoutEx(int PhysicalDrive)
{

DRIVE_LAYOUT_INFORMATION_EX lie = default(DRIVE_LAYOUT_INFORMATION_EX);
PARTITION_INFORMATION_EX[] pies = null;
using (SafeFileHandle hDevice =
NativeMethods.CreateFile("\\\\.\\PHYSICALDRIVE" + PhysicalDrive, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero))
{
if (hDevice.IsInvalid)
throw new Win32Exception();
// Must run as administrator, otherwise we get "ACCESS DENIED"

// We don't know how many partitions there are, so we have to use a blob of memory...
int numPartitions = 1;
bool done = false;
do
{
// 48 = the number of bytes in DRIVE_LAYOUT_INFORMATION_EX up to
// the first PARTITION_INFORMATION_EX in the array.
// And each PARTITION_INFORMATION_EX is 144 bytes.
int outBufferSize = 48 + (numPartitions * 144);
IntPtr blob = default(IntPtr);
int bytesReturned = 0;
bool result = false;

try
{
blob = Marshal.AllocHGlobal(outBufferSize);
result = NativeMethods.DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, IntPtr.Zero, 0, blob, outBufferSize, refbytesReturned, IntPtr.Zero);
// We expect that we might not have enough room in the output buffer.
if (result == false)
{
// If the buffer wasn't too small, then something else went wrong.
if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER)
throw new Win32Exception();
// We need more space on the next loop.
numPartitions += 1;
}
else
{
// We got the size right, so stop looping.
done = true;
// Do something with the data here - we'll free the memory before we leave the loop.
// First we grab the DRIVE_LAYOUT_INFORMATION_EX, it's at the start of the blob of memory:
lie = (DRIVE_LAYOUT_INFORMATION_EX)Marshal.PtrToStructure(blob, typeof(DRIVE_LAYOUT_INFORMATION_EX));
// Then loop and add the PARTITION_INFORMATION_EX structures to an array.
pies = new PARTITION_INFORMATION_EX[lie.PartitionCount];
for (int i = 0; i <= lie.PartitionCount - 1; i++)
{
// Where is this structure in the blob of memory?
IntPtr offset = new IntPtr(blob.ToInt64() + 48 + (i * 144));
pies[i] = (PARTITION_INFORMATION_EX)Marshal.PtrToStructure(offset, typeof(PARTITION_INFORMATION_EX));
}
}
}
finally
{
Marshal.FreeHGlobal(blob);
}
} while (!(done));
}


DumpInfo(lie, pies);

}

private static bool IsPart0Aligned(PARTITION_INFORMATION_EX[] pies)
{
try
{
if (pies[0].StartingOffset % 4096 == 0)
{
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}

private static void DumpInfo(DRIVE_LAYOUT_INFORMATION_EX lie, PARTITION_INFORMATION_EX[] pies)
{
if (IsPart0Aligned(pies) == true) {
Console.Write( "True");
}
else
{
Console.Write("false");
}
Console.WriteLine("Partition Style: {0}", lie.PartitionStyle);
Console.WriteLine("Partition Count: {0}", lie.PartitionCount);
switch (lie.PartitionStyle)
{
case PARTITION_STYLE.MBR:
Console.WriteLine("Mbr Signature: {0}", lie.Mbr.Signature);
break;
case PARTITION_STYLE.GPT:
Console.WriteLine("Gpt DiskId: {0}", lie.Gpt.DiskId);
Console.WriteLine("Gpt StartingUsableOffset: {0}", lie.Gpt.StartingUsableOffset);
Console.WriteLine("Gpt UsableLength: {0}", lie.Gpt.UsableLength);
Console.WriteLine("Gpt MaxPartitionCount: {0}", lie.Gpt.MaxPartitionCount);
break;
default:
Console.WriteLine("RAW!");
break;
}

for (int i = 0; i <= lie.PartitionCount - 1; i++)
{
Console.WriteLine();
Console.WriteLine("Partition {0} info...", i + 1);
Console.WriteLine("-------------------");
Console.WriteLine();
var _with1 = pies[i];
Console.WriteLine("Partition style: {0}", _with1.PartitionStyle);
Console.WriteLine("Starting offset: {0}", _with1.StartingOffset);
Console.WriteLine("Partition length: {0}", _with1.PartitionLength);
Console.WriteLine("Partition number: {0}", _with1.PartitionNumber);
Console.WriteLine("Rewrite partition: {0}", _with1.RewritePartition);
switch (_with1.PartitionStyle)
{
case PARTITION_STYLE.MBR:
var _with2 = _with1.Mbr;
Console.WriteLine("Mbr PartitionType - raw value: {0}", _with2.PartitionType);
// Console.WriteLine("Mbr PartitionType - validNTFT: {0}", _with2.IsValidNTFT);
// if (_with2.IsValidNTFT)
// Console.WriteLine("Mbr PartitionType - ntft: {0}", _with2.IsNTFT);
// Console.WriteLine("Mbr PartitionType - decoded: {0}", _with2.GetPartition);
Console.WriteLine("Mbr BootIndicator : {0}", _with2.BootIndicator);
Console.WriteLine("Mbr RecognizedPartition : {0}", _with2.RecognizedPartition);
Console.WriteLine("Mbr HiddenSectors : {0}", _with2.HiddenSectors);
break;
case PARTITION_STYLE.GPT:
var _with3 = _with1.Gpt;
Console.WriteLine("Gpt PartitionType: {0}", _with3.PartitionType);
Console.WriteLine("Gpt PartitionId: {0}", _with3.PartitionId);
Console.WriteLine("Gpt Attributes: {0}", _with3.Attributes);
Console.WriteLine("Gpt Name: {0}", _with3.Name);
break;
case PARTITION_STYLE.RAW:
Console.WriteLine("RAW!");
break;
}

}
}

}

Simples! lol!

2 comments:

  1. Awesome. This is the best working C# sample to my knowledge.

    ReplyDelete

Search Brian Hehir's sites

Loading