28 December 2008

Make a bootable USB Flash Drive from a floppy image file (VB.Net)

To do this you just need a handle to the drive (the device handle), and then use WriteFile to copy the image to the drive. Here's a rough example.

At the moment it rounds the byte array containing the image file up to the nearest 512 bytes, as otherwise it returns "invalid parameter".

Imports System.IO
Imports System.Text

Public Class Form1

    Private sourceFile As FileInfo

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
        RefreshDrives()
    End Sub

    Private Sub btnBrowse_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles btnBrowse.Click
        Using ofd As New OpenFileDialog
            ofd.Title = "Select disk image file"
            ofd.Multiselect = False
            Dim result As DialogResult = ofd.ShowDialog
            If result = Windows.Forms.DialogResult.OK Then
                Try
                    sourceFile = New FileInfo(ofd.FileName)
                Catch ex As Exception
                    MessageBox.Show(ex.Message)
                    sourceFile = Nothing
                End Try
            End If
        End Using
        If sourceFile IsNot Nothing Then
            Me.TextBox1.Text = sourceFile.ToString
        End If
        RefreshBtn_Nuke()
    End Sub

    Private Sub btnRefresh_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles btnRefresh.Click
        RefreshDrives()
    End Sub

    Private Sub RefreshDrives()
        Me.ComboBox1.Items.Clear()
        For Each di As DriveInfo In DriveInfo.GetDrives
            If di.DriveType = DriveType.Removable Then
                Me.ComboBox1.Items.Add(di)
            End If
        Next
        Me.ComboBox1.SelectedIndex = 0
        RefreshBtn_Nuke()
    End Sub

    Private Sub RefreshBtn_Nuke()
        btnNuke.Enabled = (sourceFile IsNot Nothing) AndAlso _
            Me.ComboBox1.Items.Count > 0
    End Sub

    Private Sub btnNuke_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnNuke.Click
        Dim result As DialogResult
        Dim drive As String = Me.ComboBox1.Text
        Dim sb As New StringBuilder
        sb.AppendLine("Do you really want to erase drive " & drive & "?")
        sb.AppendLine("Doing so will DESTROY ALL EXISTING DATA ON DRIVE " & drive & "!")
        result = MessageBox.Show(sb.ToString, _
                                 "Destroy Drive " & drive & " ?", _
                                 MessageBoxButtons.YesNo, _
                                MessageBoxIcon.Exclamation, _
                                 MessageBoxDefaultButton.Button2)
        If result = Windows.Forms.DialogResult.Yes Then
            Dim dest As DriveInfo = TryCast(Me.ComboBox1.SelectedItem, DriveInfo)
            If sourceFile IsNot Nothing AndAlso dest IsNot Nothing Then
                Dim written As Integer = Win32.CopyToDevice(sourceFile, dest)
                MessageBox.Show("Wrote: " & written & " bytes to " & drive, "Done", _
                                MessageBoxButtons.OK, MessageBoxIcon.Information)
                Application.Exit()
            Else
                RefreshBtn_Nuke()
            End If
        End If
    End Sub

End Class

 

Option Strict On
Option Explicit On

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

Public Class Win32

    Private Const GenericRead As UInteger = &H80000000UI
    Private Const GenericWrite As UInteger = &H40000000
    Private Const FileShareRead As UInteger = 1
    Private Const Filesharewrite As UInteger = 2
    Private Const OpenExisting As UInteger = 3
    Private Const IoctlVolumeGetVolumeDiskExtents As UInteger = &H560000
    Private Const IncorrectFunction As UInteger = 1
    Private Const ErrorInsufficientBuffer As UInteger = 122

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

        <DllImport("kernel32", SetLastError:=True)> _
        Public Shared Function DeviceIoControl( _
            ByVal hVol As SafeFileHandle, _
            ByVal controlCode As UInteger, _
            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 UInteger, _
            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

        <DllImport("kernel32", SetLastError:=True)> _
        Public Shared Function SetFilePointer( _
            ByVal hFile As SafeFileHandle, _
            ByVal lDistanceToMove As Integer, _
            ByRef lpDistanceToMoveHigh As Integer, _
            ByVal dwMoveMethod As Integer) As UInteger
        End Function

        ' you must write the whole inBuffer.
        <DllImport("kernel32", SetLastError:=True, CharSet:=CharSet.Ansi)> _
        Public Shared Function WriteFile( _
          ByVal hVol As SafeFileHandle, _
          <MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=3)> _
          ByVal inBuffer() As Byte, _
          ByVal numberOfBytesToWrite As Integer, _
          ByRef numberOfBytesWritten 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

    Public Shared Function CopyToDevice(ByVal source As FileInfo, _
                                        ByVal destination As DriveInfo) As Integer
        Dim physicalDrives As List(Of String) = GetPhysicalDriveStrings(destination)
        If physicalDrives.Count > 1 Then
            Throw New Exception("The volume spans multiple disks!")
        End If
        If physicalDrives.Count = 0 Then
            Throw New Exception("Could not find the physical drive!")
        End If
        Dim physicalDrive As String = physicalDrives(0)
        Dim sourceFile As String = source.FullName
        Dim driveHandle As SafeFileHandle = Nothing
        Try
            driveHandle = NativeMethods.CreateFile( _
                physicalDrive, GenericRead Or GenericWrite, _
                FileShareRead Or Filesharewrite, _
                IntPtr.Zero, OpenExisting, 0, IntPtr.Zero)
            Dim bytes() As Byte = My.Computer.FileSystem.ReadAllBytes(source.FullName)
            Dim remainder As Integer = 512 - (bytes.Length Mod 512)
            If remainder < 512 Then
                ReDim Preserve bytes(bytes.Length + remainder - 1)
            End If
            Dim written As Integer = 0
            NativeMethods.SetFilePointer(driveHandle, 0, 0, 0)
            Dim result As Boolean = NativeMethods.WriteFile(driveHandle, _
                bytes, bytes.Length, written, IntPtr.Zero)
            If result = False Then Throw New Win32Exception
            Return written
        Finally
            If driveHandle IsNot Nothing Then
                If driveHandle.IsInvalid = False Then
                    If driveHandle.IsClosed = False Then
                        driveHandle.Close()
                    End If
                End If
                driveHandle.Dispose()
            End If
        End Try

    End Function

    ' 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
            ' 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

1 comment:

  1. Prompt, how to read from raw disk? Thank you for your code, I learned how to write. I want to read and save to file.

    ReplyDelete