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