Showing posts with label P/Invoke. Show all posts
Showing posts with label P/Invoke. Show all posts

12 February 2011

Getting Hard disk drive info with DeviceIOControl

It’s a pretty popular question on forums – how do I get the serial number for a hard disk drive. The serial number is then used for some form of homebrew security. The consensus amongst those who’ve been programming a while is that it is a waste of time and effort, unreliable and not particularly good at securing your programs…

Anyway, as a fun challenge I had a go at getting the serial number from my laptops ATA-attached drive, by using DeviceIOControl to send the IDENTIFY DEVICE ATA command, as defined in the ATA spec. This won’t work on SCSI drives, or raid or …,… ,…, so it’s not reliable and isn’t the answer for those of you looking for a unique id (there isn’t a reliable unique id).

Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Imports System.Security
Imports System.ComponentModel
Imports System.Text
 
Module Module1
 
    <SuppressUnmanagedCodeSecurity()> _
    Private Class NativeMethods
        <DllImport("kernel32", SetLastError:=True)> _
        Public Shared Function CreateFile( _
         ByVal FileName As String, _
         ByVal DesiredAccess As Integer, _
         ByVal ShareMode As Integer, _
         ByVal SecurityAttributes As IntPtr, _
         ByVal CreationDisposition As Integer, _
         ByVal FlagsAndAttributes As Integer, _
         ByVal hTemplateFile As IntPtr) As SafeFileHandle
        End Function
 
        <DllImport("kernel32.dll", SetLastError:=True)> _
        Friend Shared Function DeviceIoControl( _
            ByVal deviceHandle As SafeFileHandle, _
            ByVal controlCode As Integer, _
            ByRef inBuffer As ATA_PASS_THROUGH_EX_WITH_BUFFERS, _
            ByVal inBufferSize As Integer, _
            ByRef outBuffer As ATA_PASS_THROUGH_EX_WITH_BUFFERS, _
            ByVal outBufferSize As Integer, _
            ByRef bytesReturned As Integer, _
            ByVal overlapped1 As IntPtr) As Boolean
        End Function
    End Class
 
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure ATA_PASS_THROUGH_EX
        Public Length As Short
        Public AtaFlags As Short
        Public PathId As Byte
        Public TargetId As Byte
        Public Lun As Byte
        Public ReservedAsUchar As Byte
        Public DataTransferLength As Integer
        Public TimeOutValue As Integer
        Public ReservedAsUlong As Integer
        Public DataBufferOffset As IntPtr
        <MarshalAs(UnmanagedType.ByValArray, sizeconst:=8)> _
        Public PreviousTaskFile() As Byte
        <MarshalAs(UnmanagedType.ByValArray, sizeconst:=8)> _
        Public CurrentTaskFile() As Byte
    End Structure
 
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure ATA_PASS_THROUGH_EX_WITH_BUFFERS
        Public Apt As ATA_PASS_THROUGH_EX
        <MarshalAs(UnmanagedType.ByValArray, sizeconst:=512)> _
        Public Data() As Byte
    End Structure
 
    Sub Main()
        Console.WriteLine("Enter a drive letter")
        Dim letter As Char = Console.ReadKey.KeyChar
        Console.WriteLine()
        Const GenericRead As Integer = &H80000000
        Const GenericWrite As Integer = &H40000000
        Const FileShareRead As Integer = 1
        Const FileShareWrite As Integer = 2
        Const OpenExisting As Integer = 3
        Dim drivePath As String = String.Concat("\\.\" & letter & ":")
        Console.WriteLine("Trying path: " & drivePath)
        Using driveHandle As SafeFileHandle = NativeMethods.CreateFile( _
         drivePath, _
         GenericRead Or GenericWrite, _
         FileShareRead Or FileShareWrite, _
         IntPtr.Zero, _
         OpenExisting, _
         0, _
         IntPtr.Zero)
            If driveHandle.IsInvalid Then
                Console.WriteLine("CreateFile ERROR: " & (New Win32Exception).Message)
                Console.ReadKey()
                Return
            End If
            Dim apex As New ATA_PASS_THROUGH_EX
            apex.Length = Marshal.SizeOf(apex)
            apex.AtaFlags = 2 ' ATA_FLAGS_DATA_IN
            apex.DataTransferLength = 512 ' The command returns a 512 byte package of info.
            apex.TimeOutValue = 10 ' 10 second timeout.
            apex.DataBufferOffset = Marshal.OffsetOf(GetType(ATA_PASS_THROUGH_EX_WITH_BUFFERS), "Data")
            apex.CurrentTaskFile = New Byte(7) {} ' This contains the command we are requesting.
            apex.CurrentTaskFile(6) = &HEC        ' <-- the command "IDENTIFY DEVICE"
            Dim apexb As New ATA_PASS_THROUGH_EX_WITH_BUFFERS
            apexb.Apt = apex
            Dim inBufferSize As Integer = Marshal.SizeOf(GetType(ATA_PASS_THROUGH_EX_WITH_BUFFERS))
            Dim bytesReturned As Integer
            Const IOCTL_ATA_PASS_THROUGH As Integer = &H4D02C
            Dim result As Boolean = NativeMethods.DeviceIoControl(driveHandle, IOCTL_ATA_PASS_THROUGH, _
                apexb, inBufferSize, apexb, inBufferSize, bytesReturned, IntPtr.Zero)
            If result = False Then
                Console.WriteLine("DeviceIOControl ERROR: " & (New Win32Exception).Message)
                Console.ReadKey()
                Return
            End If
            DumpString("S/N: ", apexb.Data, 20, 20)
            DumpString("Firmware: ", apexb.Data, 46, 8)
            DumpString("Model: ", apexb.Data, 54, 40)
            Console.WriteLine("Press any key")
            Console.ReadKey()
        End Using
    End Sub
 
    Private Sub DumpString(msg As String, bytes() As Byte, offset As Integer, length As Integer)
        ' The strings are slightly weird - endianness? If you use ASCII.GetBytes then each character 
        ' pair is reversed.
        Dim sb As New StringBuilder(msg & " '")
        For i As Integer = offset To offset + length - 1 Step 2
            sb.Append(Chr(bytes(i + 1)))
            sb.Append(Chr(bytes(i)))
        Next
        sb.Append("'"c)
        Console.WriteLine(sb.ToString)
    End Sub
 
End Module

Output on my laptop:

Enter a drive letter
c
Trying path: \\.\c:
S/N:  '            5TG077W9'
Firmware:  'DE14    '
Model:  'ST9160411ASG                            '
Press any key

15 January 2009

Enable/Disable a device programmatically with VB.Net using the setup api.

Very quickly...

Check in device manager to see if the device has "Disable" as an option when you R click it. If so then look at the properties, and find the "class guid" and "device instance id".

1) Get a handle to a device info set using SetupDiGetClassDevs - this will get all devices in a class.
2) Get device info data for each device in the class using SetupDiEnumDeviceInfo
3) Get the device instance id for each device using the device info data from (2) and SetupDiGetDeviceInstanceId.
4) Fill in a structure to say you want a property change and call SetupDiSetClassInstallParams. This sets the property in the device info set.
5) Call SetupDiCallClassInstaller to get the installer to make the changes stick.

16 December 2008

Determine the layout of a structure for .Net Platform Invoke

I was trying to create a signature for NOTIFYICONDATA, and decided to see what I could find out about it with C++.

1) start a new c++ CLR Console project in visual studio. Name it whatever you like. 2) Alter stdafx.h:

#pragma once
#define STRICT
#include <windows.h>   // common Win32 stuff
#include <commctrl.h>  // defines the structure we are interseted in
#include <stddef.h>    // we use offsetof
using namespace System;  // .Net types

3) in main, you can create a structure and determine its size. You can also find the offset for each field in the structure. Then you can swap the build to x64 and see what size they should be on x64 systems. This is definitely useful for when you want to create a managed C# or VB.Net version of the structure.

#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args)
{
    NOTIFYICONDATA tbb;
    int i = sizeof(tbb);
    Console::WriteLine(i);   
    Console::WriteLine(offsetof(NOTIFYICONDATA,cbSize));
    Console::WriteLine(offsetof(NOTIFYICONDATA,hWnd));
    Console::WriteLine(offsetof(NOTIFYICONDATA,uID));
    Console::WriteLine(offsetof(NOTIFYICONDATA,uFlags));
    Console::WriteLine(offsetof(NOTIFYICONDATA,uCallbackMessage));
    Console::WriteLine(offsetof(NOTIFYICONDATA,hIcon));
    Console::WriteLine(offsetof(NOTIFYICONDATA,szTip));
    Console::WriteLine(offsetof(NOTIFYICONDATA,dwState));
    Console::WriteLine(offsetof(NOTIFYICONDATA,dwStateMask));
    Console::WriteLine(offsetof(NOTIFYICONDATA,szInfo));
    Console::WriteLine(offsetof(NOTIFYICONDATA,uTimeout));
    Console::WriteLine(offsetof(NOTIFYICONDATA,szInfoTitle));
    Console::WriteLine(offsetof(NOTIFYICONDATA,dwInfoFlags));
    Console::WriteLine(offsetof(NOTIFYICONDATA,guidItem));
    Console::WriteLine(offsetof(NOTIFYICONDATA,hBalloonIcon));
    Console::ReadKey();
    return 0;
}
Ugly, but it works.

10 December 2008

VB.Net Global keyboard hook to detect "print screen" keypress

As described...

Option Strict On
Option Explicit On

Imports System.Runtime.InteropServices

Public Class Form1

    Private Const WH_KEYBOARD_LL As Integer = 13
    Private Const WM_KEYUP As Integer = &H101
    Private Const WM_SYSKEYUP As Integer = &H105
    Private proc As LowLevelKeyboardProcDelegate = AddressOf HookCallback
    Private hookID As IntPtr

    Private Delegate Function LowLevelKeyboardProcDelegate(ByVal nCode As Integer, ByVal wParam As IntPtr, _
        ByVal lParam As IntPtr) As IntPtr

    <DllImport("user32")> _
    Private Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal lpfn As LowLevelKeyboardProcDelegate, _
        ByVal hMod As IntPtr, ByVal dwThreadId As UInteger) As IntPtr
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function UnhookWindowsHookEx(ByVal hhk As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function CallNextHookEx(ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As IntPtr, _
        ByVal lParam As IntPtr) As IntPtr
    End Function

    <DllImport("kernel32.dll", CharSet:=CharSet.Unicode)> _
    Private Shared Function GetModuleHandle(ByVal lpModuleName As String) As IntPtr
    End Function

    Sub New()
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        ' Add any initialization after the InitializeComponent() call.
        hookID = SetHook(proc)
    End Sub

    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
        UnhookWindowsHookEx(hookID)
    End Sub

    Private Function SetHook(ByVal proc As LowLevelKeyboardProcDelegate) As IntPtr
        Using curProcess As Process = Process.GetCurrentProcess()
            Using curModule As ProcessModule = curProcess.MainModule
                Return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0)
            End Using
        End Using
    End Function

    Private Function HookCallback(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
        ' we check keyup for standard printscreen, and syskeyup incase it is alt+printscreen
        ' we aren't checking keydown, as that fires before the screenshot is taken.
        If nCode >= 0 AndAlso (wParam.ToInt32 = WM_KEYUP OrElse wParam.ToInt32 = WM_SYSKEYUP) Then
            Dim vkCode As Integer = Marshal.ReadInt32(lParam)
            If vkCode = Keys.PrintScreen Then
                Dim data As IDataObject = Clipboard.GetDataObject()
                If data.GetDataPresent(GetType(Bitmap)) Then
                    Me.BackgroundImage = DirectCast(data.GetData(GetType(Bitmap)), Bitmap)
                End If
            End If
        End If
        Return CallNextHookEx(hookID, nCode, wParam, lParam)
    End Function

End Class

SetClipboardViewer API VB.NET

This registers a form as a clipboard viewer. It then recieves notification when something happens to the clipboard. Some post on the msdn forum. It mentioned: http://www.radsoftware.com.au/articles/ClipboardMonitor_VB.txt, which I've tidied up to my liking (a stray long, a strange cast removed, you can't override Dispose (could you before?))
Option Strict On
Option Explicit On

Imports System.Runtime.InteropServices

Public Class Form1

   Private Const WM_DRAWCLIPBOARD As Integer = &H308
   Private Const WM_CHANGECBCHAIN As Integer = &H30D

   Private mNextClipBoardViewerHWnd As IntPtr
   Private Event OnClipboardChanged()

   <DllImport("user32")> _
   Private Shared Function SetClipboardViewer(ByVal hWnd As IntPtr) As IntPtr
   End Function

   <DllImport("user32")> _
   Private Shared Function ChangeClipboardChain(ByVal hWnd As IntPtr, ByVal hWndNext As IntPtr) As _
       <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function

   <DllImport("user32")> _
   Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, _
       ByVal lParam As IntPtr) As IntPtr
   End Function

   Sub New()
       InitializeComponent()
       mNextClipBoardViewerHWnd = SetClipboardViewer(Me.Handle)
       AddHandler Me.OnClipboardChanged, AddressOf ClipBoardChanged
   End Sub

   Protected Overrides Sub WndProc(ByRef m As Message)
       Select Case m.Msg
           Case WM_DRAWCLIPBOARD
               RaiseEvent OnClipboardChanged()
               SendMessage(mNextClipBoardViewerHWnd, m.Msg, m.WParam, m.LParam)

           Case WM_CHANGECBCHAIN
               If m.WParam.Equals(mNextClipBoardViewerHWnd) Then
                   mNextClipBoardViewerHWnd = m.LParam
               Else
                   SendMessage(mNextClipBoardViewerHWnd, m.Msg, m.WParam, m.LParam)
               End If
       End Select
       MyBase.WndProc(m)
   End Sub

   Private Sub ClipBoardChanged()
       Dim data As IDataObject = Clipboard.GetDataObject()
       If data.GetDataPresent(GetType(Bitmap)) Then
           Me.BackgroundImage = DirectCast(data.GetData(GetType(Bitmap)), Bitmap)
       End If
   End Sub

   Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
       ChangeClipboardChain(Me.Handle, mNextClipBoardViewerHWnd)
   End Sub

End Class

To use: start a few instances of the program. Copy an image into the clipboard - the backgrounds of the forms should change. Then close one of the forms and change the image in the clipboard - all the backgrounds should change.

30 November 2008

Read CID and CSD C# implementation.

Ok, here goes. It's pretty huge, so I'll just post the important bits and post a download link to the source at the bottom. I've not yet started on getting overlap to work. YOU HAVE TO SET THE BUILD TARGET TO x86. (if you have an express edition then you'll need to expose the configuration manager first - its in the settings somewhere - "show advanced build options" or something).

(Remember this is Windows XP/Vista, Admin privileges required, build target must be x86, SD card must be attached via an SD reader connected directly to the PCI host - no USB reader...)

First up...

ENUMS - Boring they are in the header files in the WDK

// SD_COMMAND_CLASS
public enum SdCommandClass : uint
{
    Standard,                       // SDCC_STANDARD
    AppCmd                          // SDCC_APP_CMD
};

// SD_TRANSFER_DIRECTION
public enum SdTransferDirection : uint
{
    Unspecified,                    // SDTD_UNSPECIFIED
    Read,                           // SDTD_READ
    Write                           // SDTD_WRITE
};

// SD_TRANSFER_TYPE
public enum SdTransferType : uint
{
    Unspecified,                    // SDTT_UNSPECIFIED
    CmdOnly,                        // SDTT_CMD_ONLY
    SingleBlock,                    // SDTT_SINGLE_BLOCK
    MultiBlock,                     // SDTT_MULTI_BLOCK
    MultiBlockNoCmd12               // SDTT_MULTI_BLOCK_NO_CMD12
};

// SD_RESPONSE_TYPE
public enum SdResponseType : uint
{
    Unspecified,                    // SDRT_UNSPECIFIED
    None,                           // SDRT_NONE
    R1,                             // SDRT_1
    R1b,                            // SDRT_1B
    R2,                             // SDRT_2
    R3,                             // SDRT_3
    R4,                             // SDRT_4
    R5,                             // SDRT_5
    R5b,                            // SDRT_5B
    R6                              // SDRT_6
};

// SFFDISK_DCMD
public enum SffdiskDcmd : uint
{
    GetVersion,                     // SFFDISK_DC_GET_VERSION
    LockChannel,                    // SFFDISK_DC_LOCK_CHANNEL
    UnlockChannel,                  // SFFDISK_DC_UNLOCK_CHANNEL
    DeviceCommand                   // SFFDISK_DC_DEVICE_COMMAND
};
public enum IoCtlCode : uint
{
    SffdiskQueryDeviceProtocol = 0x71E80,       // IOCTL_SFFDISK_QUERY_DEVICE_PROTOCOL   
    SffdiskDeviceCommand = 0x79E84,             // IOCTL_SFFDISK_DEVICE_COMMAND   
    VolumeGetVolumeDiskExtents = 0x560000       // IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
};

Native Methods - There are plenty of overrides of DeviceIOControl. The last one that takes byte[] sends the sd command. Note the use of SafeHandles instead of IntPtr.

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

// overload used for querydeviceprotocol
[DllImport("kernel32", SetLastError = true)]
public extern static bool DeviceIoControl(SafeFileHandle hVol, IoCtlCode controlCode, IntPtr inBuffer, int inBufferSize, ref SffdiskQueryDeviceProtocolData outBuffer, int outBufferSize, out int bytesReturned, IntPtr overlapped);

// overload used for getting the disk extents
[DllImport("kernel32", SetLastError = true)]
public extern static bool DeviceIoControl(SafeFileHandle hVol, IoCtlCode controlCode, IntPtr inBuffer, int inBufferSize, out DiskExtents outBuffer, int outBufferSize, out int bytesReturned, IntPtr overlapped);

// overload used for getting more than 1 diskextent
[DllImport("kernel32", SetLastError = true)]
public extern static bool DeviceIoControl(SafeFileHandle hVol, IoCtlCode controlCode, IntPtr inBuffer,int inBufferSize, IntPtr outBuffer, int outBufferSize, out int bytesReturned, IntPtr overlapped);

// Overload for the CID
[DllImport("kernel32", SetLastError = true)]
public extern static bool DeviceIoControl(SafeFileHandle hVol, IoCtlCode controlCode, Byte[] inBuffer, int inBufferSize, Byte[] outBuffer, int outBufferSize, out int bytesReturned, IntPtr ovelapped);

The Structures - No fancy marshaling required. Again, see the WDK header files.

// SDCMD_DESCRIPTOR
[StructLayout(LayoutKind.Sequential)]
struct SdCmdDescriptor
{
    public Byte CommandCode;
    public SdCommandClass CmdClass;
    public SdTransferDirection TransferDirection;
    public SdTransferType TransferType;
    public SdResponseType ResponseType;
    public int GetSize()
    {
        return Marshal.SizeOf(this);
    }
}
// SFFDISK_DEVICE_COMMAND_DATA
[StructLayout(LayoutKind.Sequential)]
struct SffdiskDeviceCommandData
{
    public ushort Size;                     // 0
    public ushort Reserved;                 // 2
    public SffdiskDcmd Command;             // 4
    public ushort ProtocolArgumentSize;     // 8
    public uint DeviceDataBufferSize;       // 12
    public uint Information;                // 16   *ULONG_PTR*, Data[] Follows.
    public void Init()
    {
        this.Size = (ushort)Marshal.SizeOf(this);
    }
} 
// SFFDISK_QUERY_DEVICE_PROTOCOL_DATA
[StructLayout(LayoutKind.Sequential)]
struct SffdiskQueryDeviceProtocolData
{
    public ushort Size;
    public ushort Reserved;
    public Guid ProtocolGuid;
    public void Init()
    {
        this.Size = (ushort)Marshal.SizeOf(this);
    }
}

The class that makes the call - could do with more tidying up. CID and CSD are classes with lots of properties, the constructor takes the byte array returned by the deviceIOControl call and fills in the properties. They probably have mistakes too. See the linked code.

using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace JDMcF.SDCard
{
    // This class might get some information about an SDCard.
    // Does not work with USB SD Card Readers.
    // Does not work with some SD Bus Host Drivers.
    // Administrator privileges required.
    // I don't know if the IOCTLs work with mobile devices.
    // I don't know if I'm translating the bytes from the CID correctly.
    public class SDCard
    {
        // GUID_SFF_PROTOCOL_SD
        private static readonly Guid GuidSffProtocolSd = new Guid("AD7536A8-D055-4C40-AA4D-96312DDB6B38");

        private CID cid;
        public CID CID { get { return cid; } }

        private CSD csd;
        public CSD CSD { get { return csd; } }

        private DriveInfo driveInfo;
        public DriveInfo DriveInfo { get { return driveInfo; } }

        private string physicalDrivePath;
        public string PhysicalDrivePath { get { return physicalDrivePath; } }

        private SDCard(string physicalDrivePath, DriveInfo driveInfo)
        {
            // At first only these are initialised. It takes a second or so to
            // read the CID so we so
            // it on another thread and raise an event once done.
            this.physicalDrivePath = physicalDrivePath;
            this.driveInfo = driveInfo;
        }

        public static List<SDCard> GetSDCards()
        {
            List<SDCard> cards = new List<SDCard>();
            // There are probably ways to mount the card elsewhere,
            // so it might miss those...
            foreach (DriveInfo di in System.IO.DriveInfo.GetDrives())
            {
                // Are all SD Cards Removable? I don't know!
                if (di.DriveType == DriveType.Removable)
                {
                    // We are enumerating volumes. Volumes can span physical
                    // disks. Work out how many physical disks are involved
                    // with each volume. (seems unlikely, but I just figured
                    // out how to do this so what the heck)...
                    List<string> physicalPaths =
                        VolumeInfo.GetPhysicalDriveStrings(di);
                    foreach (string physicalPath in physicalPaths)
                    {
                        if (IsSD(physicalPath))
                        {
                            cards.Add(new SDCard(physicalPath, di));
                        }
                    }
                }
            }
            return cards;
        }

        // Send IOCTL_SFFDISK_QUERY_DEVICE_PROTOCOL to see if the handle
        // belongs to an SD Card.
        private static bool IsSD(string physicalPath)
        {
            SafeFileHandle hVol = null;
            try
            {
                hVol = NativeMethods.CreateFile(physicalPath, AccessRights.GenericRead, ShareModes.FileShareRead  ShareModes.FileShareWrite,
IntPtr.Zero, CreationDisposition.OpenExisting, 0, IntPtr.Zero);
                if (hVol.IsInvalid)
                {
                    throw new Win32Exception("Couldn't CreateFile for " +
                                             physicalPath);
                SffdiskQueryDeviceProtocolData queryData1 =
                    new SffdiskQueryDeviceProtocolData();
                queryData1.Init();
                int bytesReturned;
                bool result = NativeMethods.DeviceIoControl(hVol, IoCtlCode.SffdiskQueryDeviceProtocol, IntPtr.Zero, 0, ref queryData1, queryData1.Size, out bytesReturned, IntPtr.Zero);
                return queryData1.ProtocolGuid.Equals(GuidSffProtocolSd);
            }
            finally
            {
                if (hVol != null)
                {
                    if (!hVol.IsInvalid)
                    {
                        hVol.Close();
                    }
                    hVol.Dispose();
                }
            }
        }

        public void RefreshData()
        {
            GetRegister(Register.CID);
            GetRegister(Register.CSD);
        }

        // Send the command in the array. On return the array will have any
        // response.
        private void SendCommand(byte[] command)
        {
            SafeFileHandle hVol = null;
            try
            {
                hVol = NativeMethods.CreateFile(PhysicalDrivePath, AccessRights.GenericRead  AccessRights.GenericWrite, ShareModes.FileShareRead  ShareModes.FileShareWrite, IntPtr.Zero, CreationDisposition.OpenExisting, 0, IntPtr.Zero);
                int bytesReturned;
                bool result = NativeMethods.DeviceIoControl(hVol, IoCtlCode.SffdiskDeviceCommand, command, command.Length, command, command.Length, out bytesReturned, IntPtr.Zero);
                if (!result) throw new Win32Exception();
            }
            finally
            {
                if (hVol != null)
                {
                    if (!hVol.IsInvalid)
                    {
                        hVol.Close();
                    }
                    hVol.Dispose();
                }
            }
        }

        // Get the CID or CSD. They are almost identical commands...
        private void GetRegister(Register register)
        {
            byte[] command = null;
            SffdiskDeviceCommandData commandData = new SffdiskDeviceCommandData();
            SdCmdDescriptor commandDescriptor = new SdCmdDescriptor();
            commandData.Init();
            commandData.Command = SffdiskDcmd.DeviceCommand;
            commandData.ProtocolArgumentSize = (ushort)commandDescriptor.GetSize();
            commandData.DeviceDataBufferSize = 16;
            commandDescriptor.CommandCode = (byte)register;   // <--- Not what the documentation indicates!
            commandDescriptor.CmdClass = SdCommandClass.Standard;
            commandDescriptor.TransferDirection = SdTransferDirection.Read;
            commandDescriptor.TransferType = SdTransferType.CmdOnly;
            commandDescriptor.ResponseType = SdResponseType.R2;

            // Now get the structs into the byte[]
            command = new byte[commandData.Size + commandData.ProtocolArgumentSize + commandData.DeviceDataBufferSize];
            IntPtr hBuf = Marshal.AllocHGlobal(command.Length);
            Marshal.StructureToPtr(commandData, hBuf, true);
            IntPtr descriptorOffset = new IntPtr(hBuf.ToInt32() + commandData.Size);
            Marshal.StructureToPtr(commandDescriptor, descriptorOffset, true);
            Marshal.Copy(hBuf, command, 0, command.Length);
            Marshal.FreeHGlobal(hBuf);

            SendCommand(command);
            // Strip out the return bytes that live at the end of the command byte array.
            byte[] regBytes = new byte[16];
            Buffer.BlockCopy(command, command.Length - 16, regBytes, 0, 16);

            if (register == Register.CID)
            {
                cid = new CID(regBytes);
            }
            else
            {
                csd = new CSD(regBytes);
            }
        }
    }
}

Formatting is awful...

27 November 2008

Howto: Get the physical drive string //./PhysicalDriveX from a path

There are lots of strings that you can feed CreateFile, if we are looking at volumes and drives then they include:

The Unique Volume Name:

\\?\Volume{013eeefb-9b12-11dd-bee5-806e6f6e6963}\

You can list those at the command prompt with: "mountvol".

The mount point:

\\.\C:

If you really want to list them at the command line, then "fsutil fsinfo drives" will do the trick.

The physical drive string (or whatever it is called)

\\.\PhysicalDrive0

command: "wmic diskdrive get name,size,model"

But, how can we get all of the physical drive strings available? Well, the hint is in the CreateFile documentation.

To obtain the physical drive for a volume, open a handle to the volume and call the DeviceIoControl function with IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS. This control code returns the disk number of offset for each of the volume's extents; a volume can span disks.

In .Net then, start by enumerating all the drives, and create strings like the mount points above "\\.\X:". Use this with CreateFile to get a handle to the volume. Then call DeviceIOControl with IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS.

The Class that does the hard work:

Project type - vb.net class library (2005 or 2008) - name JdMcF.VolumeInfo:

Option Strict On
Option Explicit On

Imports Microsoft.Win32.SafeHandles
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.ComponentModel

Public Class VolumeInfo

   Private Const GenericRead As Integer = &H80000000
   Private Const FileShareRead As Integer = 1
   Private Const Filesharewrite As Integer = 2
   Private Const OpenExisting As Integer = 3
   Private Const IoctlVolumeGetVolumeDiskExtents As Integer = &H560000
   Private Const IncorrectFunction As Integer = 1
   Private Const ErrorInsufficientBuffer As Integer = 122

   Private Class NativeMethods
       <DllImport("kernel32", CharSet:=CharSet.Unicode, SetLastError:=True)> _
       Public Shared Function CreateFile( _
           ByVal fileName As String, _
           ByVal desiredAccess As Integer, _
           ByVal shareMode As Integer, _
           ByVal securityAttributes As IntPtr, _
           ByVal creationDisposition As Integer, _
           ByVal flagsAndAttributes As Integer, _
           ByVal hTemplateFile As IntPtr) As SafeFileHandle
       End Function

       <DllImport("kernel32", SetLastError:=True)> _
       Public Shared Function DeviceIoControl( _
           ByVal hVol As SafeFileHandle, _
           ByVal controlCode As Integer, _
           ByVal inBuffer As IntPtr, _
           ByVal inBufferSize As Integer, _
           ByRef outBuffer As DiskExtents, _
           ByVal outBufferSize As Integer, _
           ByRef bytesReturned As Integer, _
           ByVal overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
       End Function

       <DllImport("kernel32", SetLastError:=True)> _
       Public Shared Function DeviceIoControl( _
           ByVal hVol As SafeFileHandle, _
           ByVal controlCode As Integer, _
           ByVal inBuffer As IntPtr, _
           ByVal inBufferSize As Integer, _
           ByVal outBuffer As IntPtr, _
           ByVal outBufferSize As Integer, _
           ByRef bytesReturned As Integer, _
           ByVal overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
       End Function
   End Class

   ' DISK_EXTENT in the msdn.
   <StructLayout(LayoutKind.Sequential)> _
   Private Structure DiskExtent
       Public DiskNumber As Integer
       Public StartingOffset As Long
       Public ExtentLength As Long
   End Structure

   ' DISK_EXTENTS
   <StructLayout(LayoutKind.Sequential)> _
   Private Structure DiskExtents
       Public numberOfExtents As Integer
       Public first As DiskExtent ' We can't marhsal an array if we don't know its size.
   End Structure

   ' A Volume could be on many physical drives.
   ' Returns a list of string containing each physical drive the volume uses.
   ' For CD Drives with no disc in it will return an empty list.
   Public Shared Function GetPhysicalDriveStrings(ByVal driveInfo As DriveInfo) As List(Of String)
       Dim sfh As SafeFileHandle = Nothing
       Dim physicalDrives As New List(Of String)(1)
       Dim path As String = "\\.\" & driveInfo.RootDirectory.ToString.TrimEnd("\"c)
       Try
           sfh = NativeMethods.CreateFile(path, GenericRead, FileShareRead Or Filesharewrite, IntPtr.Zero, _
                                                          OpenExisting, 0, IntPtr.Zero)
           Dim bytesReturned As Integer
           Dim de1 As DiskExtents = Nothing
           Dim numDiskExtents As Integer = 0
           Dim result As Boolean = NativeMethods.DeviceIoControl(sfh, IoctlVolumeGetVolumeDiskExtents, IntPtr.Zero, _
                                                                 0, de1, Marshal.SizeOf(de1), bytesReturned, IntPtr.Zero)
           If result = True Then
               ' there was only one disk extent. So the volume lies on 1 physical drive.
               physicalDrives.Add("\\.\PhysicalDrive" & de1.first.DiskNumber.ToString)
               Return physicalDrives
           End If
           If Marshal.GetLastWin32Error = IncorrectFunction Then
               ' The drive is removable and removed, like a CDRom with nothing in it.
               Return physicalDrives
           End If
           If Marshal.GetLastWin32Error <> ErrorInsufficientBuffer Then
               Throw New Win32Exception
           End If           
           ' Houston, we have a spanner. The volume is on multiple disks.
           ' Untested...
           ' We need a blob of memory for the DISK_EXTENTS structure, and all the DISK_EXTENTS
           Dim blobSize As Integer = Marshal.SizeOf(GetType(DiskExtents)) + _
                                     (de1.numberOfExtents - 1) * Marshal.SizeOf(GetType(DiskExtent))
           Dim pBlob As IntPtr = Marshal.AllocHGlobal(blobSize)
           result = NativeMethods.DeviceIoControl(sfh, IoctlVolumeGetVolumeDiskExtents, IntPtr.Zero, 0, pBlob, _
                                                  blobSize, bytesReturned, IntPtr.Zero)
           If result = False Then Throw New Win32Exception
           ' Read them out one at a time.
           Dim pNext As New IntPtr(pBlob.ToInt32 + 4) ' is this always ok on 64 bit OSes? ToInt64?
           For i As Integer = 0 To de1.numberOfExtents - 1
               Dim diskExtentN As DiskExtent = DirectCast(Marshal.PtrToStructure(pNext, GetType(DiskExtent)), DiskExtent)
               physicalDrives.Add("\\.\PhysicalDrive" & diskExtentN.DiskNumber.ToString)
               pNext = New IntPtr(pNext.ToInt32 + Marshal.SizeOf(GetType(DiskExtent)))
           Next
           Return physicalDrives
       Finally
           If sfh IsNot Nothing Then
               If sfh.IsInvalid = False Then
                   sfh.Close()
               End If
               sfh.Dispose()
           End If
       End Try
   End Function

End Class

The Test project:

Project type: vb.net windows forms app (2005 or 2008) name: whatever.

Option Strict On
Option Explicit On
Option Infer Off

Imports JDMcF.VolumeInfo
Imports System.IO
Imports System.Text

Public Class Form1

   Private lv1 As New ListView

   Sub New()

       ' This call is required by the Windows Form Designer.
       InitializeComponent()

       ' Add any initialization after the InitializeComponent() call.
       With lv1
           .View = View.Details
           .Columns.Add("Root", 100, HorizontalAlignment.Left)
           .Columns.Add("Physical Drive String", 200, HorizontalAlignment.Left)
           .Dock = DockStyle.Fill
       End With
       Me.Controls.Add(lv1)       
   End Sub

   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       For Each di As DriveInfo In DriveInfo.GetDrives
           Dim drivesList As List(Of String) = VolumeInfo.GetPhysicalDriveStrings(di)

           Dim drives As New StringBuilder
           If drivesList.Count > 0 Then
               For Each s As String In drivesList
                   drives.Append(s)
                   drives.Append(", ")
               Next
               drives.Remove(drives.Length - 2, 2)
           Else
               drives.Append("n/a")
           End If
           Dim lvi As New ListViewItem(di.RootDirectory.ToString)
           lvi.SubItems.Add(drives.ToString)
           lv1.Items.Add(lvi)
       Next
   End Sub

End Class

Linky to code:

16 November 2008

C++ Interop example. FlashWindowEx

I've been using C++ occasionally instead of P/Invoke. I'm not a c++ programmer.

Here's how to use c++ express 2008 and vb.net 2008 together to call a platform method.

  1. First start C++.
  2. File->New->Project-> Project Type = CLR and use the Class Library template, set the project name to Flasher.
  3. Insert some code into Stdafx.h so that it looks like this:

    #pragma once #define STRICT #define WIN32_LEAN_AND_MEAN #include <windows.h> using namespace System;

    You can see that we have included windows.h, which will give us access to many standard c++ types, such as BOOL. Windows.h is huge, so we set lean and mean too, which strips some infrequently used items -- they won't be included. #define STRICT is similar to Option Strict in vb.net. It stops you being lazy!

  4. Next we need to declare a class and a static method (which is a vb.net Shared method):

    // Flasher.h

    #pragma once using namespace System; namespace Flasher { public ref class Flasher { public: static void Flash(IntPtr); }; }
  5. Now we add method code in the cpp file:

    // This is the main DLL file.

    #include "stdafx.h" #include "Flasher.h" namespace Flasher { void Flasher::Flash(IntPtr hWndManaged) { FLASHWINFO info; ZeroMemory(&info, sizeof(FLASHWINFO)); info.uCount = 5; info.dwFlags = FLASHW_CAPTION; info.dwTimeout = 0; info.hwnd = (HWND) hWndManaged.ToPointer(); info.cbSize = sizeof(info); BOOL result = FlashWindowEx(&info); }

    }

    First we include those header files, so we get all of the methods and types declared in windows.h, and our own Flash method. Next we have the Flash method. To call FlashWindowEx you send a FLASHWINFO structure. If we were going to P/Invoke this, then we would need to declare our own versions of FlashWindowEx and FLASHWINFO. Here we don't have to, as we have the header files included. At the same time, this is a .Net dll, so our Flash method will be usable from VB.Net. After declaring a FLASHWINFO variable, we zero the memory. This is because C++ doesn't do it for us, we could get all sorts of junk in the new structure's fields if we don't do this. The & in &info is telling ZeroMemory the address where the info structure lives. We tell it to flash 5 times. We tell it to flash the caption. We tell it to flash at the default rate (= the systems cursor blink rate) by setting timeout to 0. Next we need to convert the managed IntPtr that is the windows Handle, into the unmanaged HWND type. This involves the ToPointer method, and a cast. Finally we set the structure size and send it off.

  6. Does it build?

  7. No it doesn't. Unresolved token, and unresolved external symbol. There is a linker error, look at the msdn page for FlashWindowEx: Library: Use User32.lib This means that we need to link against User32.lib. We aren't, so we get the error. Right click the project name "Flasher" in the solution explorer. Click properties. Look at: Configuration Properties/Linker/Input/Additional Dependencies Make sure the additional dependencies line is selected and click the ellipsis (...). A quick glance makes you think user32 is linked, but actually the list is a list of thinks that can be inherited, but we have $(NoInherit) set. Add user32.lib to the listbox. Once done, click Ok, and you should see: Additional Dependencies: user32.lib $(NOINHERIT)

  8. Does it build?

  9. No it doesn't. This time it's my antivirus software keeping the file open, causing a file access error: "mt.exe : general error c101008d: Failed to write the updated manifest to the resource of file" One that's sorted, it builds. Add an antivirus exception for mt.exe. For me it lives here: (C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin)

  10. Start a new VB.Net project. Add a button. Click Project->Add Reference, select the browse tab, navigate to the C++ dll that was built and add that.

  11. Add the following to the button click code:

    Flasher.Flasher.Flash(Me.Handle)

  12. Does it build? For me - yes!

  13. Does it run? For me - no! I'm using Vista 64, and get: "Could not load file or assembly 'Flasher, Version=1.0.3245.7081, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format." This is fixed by setting the Target for the VB.Net program to x86, it was on AnyCPU, the dll is win32! In vb express you might need to do: Tools->Options->Projects and Solutions->General->Show Advanced Build Configurations then, you will need: Build->Configuration Manager->Active Solution Platform -- select <New> and x86

  14. It finally works.

For a simple win32 api call this is too much work. It might be worth it for performance if you are calling something a lot. If you have a complicated api, then it pays off as you don't have to make managed versions of everything. If the method is in a static library then this is a good technique.