This article will discuss how to programmatically backup data CDs under Visual Basic.NET.
If you haven't already noticed, .NET framework doesn't provide a direct method to copy from a CDRom drive to a specified destination. The following example shows that you can't directly obtain a stream over the CDRom device using the drive letter.
Dim fStream As New System.IO.FileStream("d:\", IO.FileMode.Open, _
IO.FileAccess.Read, IO.FileShare.ReadWrite, 65536)
1:
2:
Select allOpen in new window
(It halts with an UnauthorizedAccessException error.) The following try also fails:
How to Create a Stream Over the CDRom Drive
In order to obtain a stream over the CDRom drive in .NET you must use PInvoke signature with the CreateFile() function. In addition, we will be using the SafeFileHandle Class.
Most importantly we need to use both of these classes you'll need a direct access handle to the disk drive in addition you can pass the SafeFileHandle object to the FileStream class to generate the stream over the device drive.
pseudo code
Dim hDevice As Microsoft.Win32.SafeHandles.SafeFileHandle = Nothing
hDevice = CreateFile("\\.\d:", GenericRead, FileShare.ReadWrite, _
IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero)
deviceStream = New System.IO.FileStream(hDevice, FileAccess.Read, BufferSize)
1:
2:
3:
4:
Select allOpen in new window
Notice that in this call we use the format CreateFile("\\.\d:"...) this informs the system that we want to request a volume handle on that device. Remember that no trailing backslash should be specified in that prefix or you will tell Windows you are requesting a directory handle instead.
After you obtain a stream over the device drive you can get the data from the CDRom disc directly using the Stream methods Read/Write/Seek. It's also important that you use a multiple sector aligned buffer that is a multiple of the CDRom sector size.
Creating the ISO Image of the CDRom Data Disc (Source Code)
The DataDisc Class (that we'll create below) shows how to make an exact copy of the CDRom disc to an ISO file using the methods described above. The class takes things one step further by also calculating the file checksum hash value of the disc in either MD5 or SHA1.
The calculated file hash can be used to determine whether the image is in it's original working state. You can use the hash value to verify that it hasn't been corrupted.
As an example if you copy to your local disk then decide to move the image to a network drive you can verify the integrity of the .ISO image by checking that the file on the network returns the same hash value that was returned from the original copy.
It also has the benefit that you don't need re-read the same file again after it was completed rather it's calculated during the copy operation which can increase the downtime for larger files.
You may also use this hash value as part of the filename in the format hashvalue_label.iso so you always know the original checksum value without saving it to another document.
Usage:
Tip: If your using a Windows Form with the example code feel free to make use of the background worker to support progress and cancellation you may also use a seperate thread as the class uses synchronous methods.
Dim myDisc As New DataDisc
myDisc.Copy(New System.IO.DriveInfo("f:\"), _
"c:\users\username\documents\mydisc.iso", DataDisc.HashProvider.MD5)
1:
2:
3:
Select allOpen in new window
1) Create a new class named: DataDisc.vb
2) Add the following code:
Imports System.IO
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Imports System.Security.Cryptography
Public Class DataDisc
'egl1044
Private Const BufferSize As Integer = 65536 '// multiple sector aligned buffer.
Private Const GenericRead As Integer = &H80000000
Public Enum HashProvider
MD5 = 0
SHA1 = 1
End Enum
<DllImport("Kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _
Public Shared Function CreateFile(ByVal lpFileName As String, _
ByVal dwDesiredAccess As Integer, _
ByVal dwShareMode As Integer, _
ByVal lpSecurityAttributes As IntPtr, _
ByVal dwCreationDisposition As Integer, _
ByVal dwFlagsAndAttributes As Integer, _
ByVal hTemplateFile As IntPtr) As SafeFileHandle
End Function
Public Sub New()
End Sub
Public Sub Copy(ByVal discDrive As DriveInfo, ByVal imageFileName As String, ByVal provider As HashProvider)
Dim hDevice As SafeFileHandle = Nothing '// The volume handle to the CDRom device.
Dim deviceStream As FileStream = Nothing '// The device stream.
Dim imageStream As FileStream = Nothing '// The image stream.
Dim hashStream As HashAlgorithm = Nothing '// The hash stream.
Dim hashBuffer(BufferSize - 1) As Byte '// Bogus hash output buffer.
Dim hashValue As String = String.Empty '// The final hash checksum value of the image.
Dim dataBuffer(BufferSize - 1) As Byte '// multiple sector aligned buffer.
Dim dataBytesRead As Integer = 0 '// The bytes read from the stream.
Dim fOk As Boolean = True '// failure bit flag.
If discDrive IsNot Nothing Then
' Verify that the drive points to a CDRom device.
If discDrive.DriveType <> DriveType.CDRom Then
Debug.Print("Invalid CDRom device drive.")
Return
End If
' Verify that the drive has media inserted.
If Not discDrive.IsReady Then
Debug.Print("Device not ready")
Return
End If
' Open the CDRom device for direct access.
hDevice = CreateFile("\\.\" & discDrive.Name.Substring(0, 2), GenericRead, _
FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero)
' Verify the device handle is valid.
If hDevice.IsInvalid Then
Debug.Print("CreateFile failed" & Marshal.GetLastWin32Error)
Return
End If
' Prepare the streams for the copy operation.
Try
' Generate file hash checksum using either MD5 or SHA1.
hashStream = CType(IIf(CBool(provider), New SHA1CryptoServiceProvider, New MD5CryptoServiceProvider), HashAlgorithm)
' Create a stream over the CDRom device using the direct access volume handle.
deviceStream = New FileStream(hDevice, FileAccess.Read, BufferSize)
' Create a stream that will contain the CDRom data read from the device.
imageStream = New FileStream(imageFileName, _
FileMode.Create, FileAccess.Write, FileShare.None, BufferSize)
' Begin copying the the disc data to the image file.
Do
' Read disc data into multiple sector aligned buffer.
dataBytesRead = deviceStream.Read(dataBuffer, 0, BufferSize)
' Transform the data for MD5 or SHA1 hash value.
hashStream.TransformBlock(dataBuffer, 0, dataBytesRead, hashBuffer, 0)
' Write the disc data to the image file.
imageStream.Write(dataBuffer, 0, dataBytesRead)
Loop Until dataBytesRead = 0
' TransformFinalBlock must be called when finished.
hashStream.TransformFinalBlock(dataBuffer, 0, 0)
' Get the file hash checksum of the image.
If hashStream IsNot Nothing Then
hashValue = BitConverter.ToString(hashStream.Hash).Replace("-", "").ToLower
End If
Catch ex As Exception
fOk = False ' failure bit flag.
MsgBox(ex.Message)
Finally
' cleanup
If deviceStream IsNot Nothing Then
deviceStream.Close()
deviceStream.Dispose()
End If
If imageStream IsNot Nothing Then
imageStream.Close()
imageStream.Dispose()
End If
If hDevice IsNot Nothing Then
hDevice.Close()
hDevice.Dispose()
End If
End Try
If fOk Then
' TODO:\\ Work with the image file. (rename,move etc..)
Debug.Print("Copy Complete. File checksum: {0}", hashValue)
Else
' TODO:\\ delete image file.
Debug.Print("Copy Failed.")
If File.Exists(imageFileName) Then
File.Delete(imageFileName)
End If
End If
End If
End Sub
End Class
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
Select allOpen in new window
Important Things To Consider
The following method only works for Data Disc CD's these are discs such as Games, Operating System CD's, Driver CD's, Software or Installation CD's etc...
You can't use this method to Copy CD's that are Audio, DVD movies, etc. These types of media have different structures and are formatted differently.
Important, But Optional Considerations When Copying Data CDRom Discs.
- Locking the media tray to prevent accidental removal of the disc while it's being copied.
- Locking the device for exclusive access to prevent other applications from obtaining access to the disc until the copy has completed.
- Extending reads past the disc that would otherwise not be visible using the traditional methods.
- Ejecting the CDRom disc when the Copy has completed to flush internal cache that is left behind by the operating system.
- Ensure the target drive has enough available disk space to store the entire ISO Image.
I won't cover how to do the above in this article, but if you need help implementing the optional features, you can open a question and I or another EE Expert will be glad to help you.
How to Determine an Audio CD or DVD Movie
You can use the same method that the windows
Auto Play feature uses to detect different media disc types.
Audio CDAudio/Music discs that you would play in your home entertainment system, when you insert them into the computer windows reads the disc and generates track files on the fly. These files aren't actually on the disc but generated by windows only when you insert them into the computer.
' Determines that the media present in the drive is an AudioCD.
Public Function IsDiscAudioCD(ByVal rootDrive As String) As Boolean
Dim audio As String = Path.Combine(rootDrive, "track01.cda")
Return File.Exists(audio)
End Function
1:
2:
3:
4:
5:
Select allOpen in new window
DVD Movie
DVD movies can be determined by the video_ts.ifo file located in the video_ts folder.
' Determines that the media present in the drive is an DVD Movie.
Public Function IsDiscDVDMovie(ByVal rootDrive As String) As Boolean
Dim dvd As String = Path.Combine(rootDrive, "video_ts\video_ts.ifo")
Return File.Exists(dvd)
End Function
1:
2:
3:
4:
5:
Select allOpen in new window
Final thoughts...
If you're not already using Virtual CloneDrive I highly recommend that you do. It's free and works great with the example.
The utility allows you to mount the ISO images as CDRom drives. If you're running Windows 7 there is also a feature to directly burn the image to a blank_cd (Windows Disc Image burner). Just right click the .ISO file and it will be available on the context menu. Enjoy!
Slysoft's Virtual CloneDrive (freeware download) http://www.slysoft.com/en/virtual-clonedrive.html
by: egl1044 on 2010-08-31 at 23:11:15ID: 18976
The asynchronous version while more complex works just like the background worker providing many benefits such as reporting progress, completion and cancellation.
Under a Windows Form Project add the following controls to the Form.
2 Buttons
1 ProgressBar
' Usage:
Public Class Form1
Dim WithEvents myDisc As New DataDisc
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
' Copy Button
Dim myDiscDrive As New System.IO.DriveInfo("d:\")
If Not myDisc.IsBusy Then
myDisc.BeginCopy(myDiscDri
Else
Debug.Print("Copy already in progress.")
End If
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
' Cancel Button
myDisc.Cancel()
End Sub
Private Sub myDisc_CopyCompleted(ByVal
If e.Cancelled Then
Debug.Print("Cancelled!")
' TODO:// When the copy has been cancelled.
' for example you can delete the image file
' from the disk.
DeleteImageFile(e.ImageFil
ElseIf e.Error IsNot Nothing Then
Debug.Print("Error! {0}", e.Error.Message)
' TODO:// When an error occurs at any time during the
' copy operation.
DeleteImageFile(e.ImageFil
Else
Debug.Print("Complete! image={0} hash={1}", e.ImageFileName, e.Hash)
' TODO:// When the copy is completed.
' for example you can rename the image file.
End If
' Reset progress bar value.
ProgressBar1.Value = 0
End Sub
Private Sub myDisc_ProgressChanged(ByV
' Copy Progress
ProgressBar1.Value = e.ProgressPercentage
End Sub
Private Sub DeleteImageFile(ByVal imageFile As String)
If System.IO.File.Exists(imag
System.IO.File.Delete(imag
End If
End Sub
End Class
Select allOpen in new window