Click here to Skip to main content
Email Password   helpLost your password?
burn

Introduction

If you've seen my previous article, How to Create Optical File Images using IMAPIv2.0, you might expect me to do something with that file image, like... burn it. It is the same Image Mastering API v2.0 I'm using to achieve this goal, so you might want to install it before running this application. There might be other burning engines available, but I've chosen to use this API because it is likely to have long term support from Microsoft. Using COM Interop to wrap the native components has some quirks I've tried to fix, but it is not very difficult, thanks to the samples from Microsoft. If you were interested only in burning folders and files on to media and skip the file image creation, you will be better served by Eric Haddan's great article. I recommend you check the articles mentioned above should you need a better understanding of the inner workings or COM Interop issues. I've made only minor changes to the code from my previous article, and I also got some cues from the sources I have mentioned below.

Background

If you are wondering why you would need to burn an ISO image when you could cold burn the files directly, then you should be aware that you cannot easily create every kind of format using IMAPI 2.0. However, there is no problem in burning a file stream if the recorder supports the media type. In other words, if you have an ISO file containing a DVD movie, you will be able to burn it, but you can't create a proper DVD disk even if you copy all the files from the media. While you might be able to play the latter on your computer and a few new players, most DVD players won't play it, because they expect a customized UDF format. This is the case when burning an existing ISO file image created with other tools overcomes this issue.

Burning a File Optical Image

Burning an optical image boils down to writing a stream on your media. The code for writing and formatting is similar to the code in Eric's article, with a few changes. Getting the stream interface is done using the ImageRepository.LoadCOMStream that was described in my previous article. I've packaged most of the burning capability in a helper class called BurnHelper, and below is its WriteStream method:

public int WriteStream(System.Runtime.InteropServices.ComTypes.IStream stream, 
                       string initburner, string clientName, 
                       bool forceMediaToBeClosed, int speed, bool eject)
{
    MsftDiscRecorder2 discRecorder = null;
    MsftDiscFormat2Data discFormat = null;
    int result = -1;
    try
    {
        discRecorder = new MsftDiscRecorder2();
        discRecorder.InitializeDiscRecorder(initburner);
        discFormat = new MsftDiscFormat2Data();
        //remove the comment for next 2 lines
        discFormat.Recorder = discRecorder;
        discRecorder.AcquireExclusiveAccess(true, clientName);

        // initialize the IDiscFormat2Data
        discFormat.ClientName = clientName;
        discFormat.ForceMediaToBeClosed = forceMediaToBeClosed;

        // add the Update event handler
        discFormat.Update += DiscFormatData_Update;

        //this is how it worked for my burner
        //speed = 0 => minimum speed descriptor in update
        // 0 < speed < minimum speed descriptor => half of minimum
        //             speed descriptor in update
        // minimum speed descriptor <= speed < next speed
        //         descriptor => minimum speed descriptor in update
        // next speed descriptor <= speed  => next speed descriptor in update
        discFormat.SetWriteSpeed(speed, true);

        //write the stream
        discFormat.Write(stream);
        if (_backgroundWorker.CancellationPending)
        {
            return 1;
        }
        if (eject)
        {
            //wait to flush all the content on the media
            IMAPI_FORMAT2_DATA_MEDIA_STATE state = 
              IMAPI_FORMAT2_DATA_MEDIA_STATE.IMAPI_FORMAT2_DATA_MEDIA_STATE_UNKNOWN;
            while (state == IMAPI_FORMAT2_DATA_MEDIA_STATE.
                            IMAPI_FORMAT2_DATA_MEDIA_STATE_UNKNOWN && 
                            !_backgroundWorker.CancellationPending)
            {
                try
                {
                    state = discFormat.CurrentMediaStatus;
                }
                catch (Exception)
                {

                    state = IMAPI_FORMAT2_DATA_MEDIA_STATE.
                            IMAPI_FORMAT2_DATA_MEDIA_STATE_UNKNOWN;
                    System.Threading.Thread.Sleep(3000);
                }
            }
            if (!_backgroundWorker.CancellationPending)
                discRecorder.EjectMedia();
        }
        result = 0;
    }
    finally
    {
        if (_backgroundWorker.CancellationPending)
            result = 1;
        if (discFormat != null)
        {
            // remove the Update event handler
            //
            discFormat.Update -= DiscFormatData_Update;
            Marshal.FinalReleaseComObject(discFormat);
        }
        if (discRecorder != null)
        {
            //discRecorder.EnableMcn();
            discRecorder.ReleaseExclusiveAccess();
            Marshal.FinalReleaseComObject(discRecorder);
        }
    }
    return result;
}

If you see my code comments relative to Write Speed, you can notice that there might be speeds that are not shown as descriptors by IMAPI. You might be able to discover them by using other numbers than the ones provided by the descriptors that will show up in the progress update UI. I also had to delay the media being ejected until everything was flushed on the media, just to avoid a crash.

Detecting Media Changes

Synchronizing the media with the UI should not be taken for granted. Some systems might have the media notification turned off by Active Directory Policy or registry settings. I've originally tried using WMI to detect these events, but I did not get any notification when inserting blank media. Hence, I ended up using the old fashioned WM_DEVICECHANGE Windows message to get the media changes. There is quite a bit of code to achieve this in the DeviceMonitor class, which was based on this article as the starting point. Below is the most important method of this class:

protected override void WndProc(ref Message m)
{
    // There has been a change in the devices
    const int WM_DEVICECHANGE = 0x0219;
    switch (m.Msg)
    {
        case WM_DEVICECHANGE:
            {
                
                if (m.LParam == IntPtr.Zero)
                    break;
                DEV_BROADCAST_VOLUME vol = (DEV_BROADCAST_VOLUME)
                  Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));

                if ((vol.dbcv_devicetype == _dt) && (vol.dbcv_flags == _vf))
                    PostMessage(new HandleRef(this, Handle), 
                                POST_MSG_DEVICECHANGE, m.WParam, 
                                (IntPtr)vol.dbcv_unitmask);
            }
            break;
        default:
            if (POST_MSG_DEVICECHANGE == m.Msg)
            {
                DeviceEventBroadcast ev = (DeviceEventBroadcast)m.WParam.ToInt32();
                List<char> drives = DriveNames((uint)m.LParam);
                List<char>notifyDrives = new List<char>(1);
                _drives.ForEach(delegate(char drv)
                {
                    if (drives.Contains(drv)) notifyDrives.Add(drv);
                });
                if (DeviceAction != null && notifyDrives.Count > 0)
                    DeviceAction(notifyDrives, ev);
            }
            break;
    }
    base.WndProc(ref m);
}

If you are wondering why I needed to repost the original WM_DEVICECHANGE to POST_MSG_DEVICECHANGE, it is because WM_DEVICECHANGE occurs before IMAPI becomes aware of the media change, and would crash otherwise.

Other Features

Unlike the previous version, I can't use the only UI thread by pumping messages with Application.DoEvents because the IMAPI events are too scarce for a smooth user experience, so I had to use the BackgroundWorker for burning. You have the option to format RW media, and sometimes even to force formatting unrecognizable media, a simple feature I've been unable to find in other burning programs. You'll also find many new features and enhancements when compared to my previous article. I have to mention that rebooting, sleeping, and the like are based on the mentalis code, and the making of the media bootable by adding a file has a bug I could not fix yet. If you want to take a stab at it, the code is in the ImageRepositoty.CreateBootDisc method, so feel free to tinker with it. I tried both IMAPI2FS.FsiStream and COMTypes.IStream as arguments, but to no avail. If you find a way around, please let me know.

Adding a boot image option is done by checking the checkbox on the ‘Select Files’ tab of the application, as shown in the image below:

Files and Folders

How to Use It

I did not test it extensively because of my limited environment, so what worked on my system might not work on yours. Creating an optical file image is the same as in my previous article sans the optional bootable option. Burning a file image from the ‘Build Image' tab should be quite easy, you have to select the image and burner, optionally the speed, eject, and close the media, and hit the 'Burn Image' button. You should be able to cancel the operation if you wish, but you might damage the media if it is not RW.

Reusing the Code

The solution has two projects, to separate the UI from the classes that actually do the image creation and burning. You can easily reuse this functionality if you add a reference to the ISOImage.dll or to the OpticalImage project that comes in two flavours for Visual Studio 2005 and 2008. You must install the Image Mastering API v2.0 if you are not running on Vista, to use it.

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralE_IMAPI_RECORDER_MEDIA_BUSY Error
jaredharley
16:37 16 Jan '10  
I've been using your code for a while with no problems, but I had to add some handling code this evening because I began receiving the E_IMAPI_RECORDER_MEDIA_BUSY |(HRESULT)0xC0AA0207 error.

According to the IMAPI return values, "The drive reported that it is performing a long-running operation, such as finishing a write. The drive may be unusable for a long period of time." This error doesn't prevent the disc from being created, just from the proper status message from being sent back to the GUI and the percentage done from being calculated.

I was seeing this in the catch of DiscFormatData_Update.

I handled it with the following code inside the catch
if (e.Message.Contains("0xC0AA0207"))
{
_backgroundWorker.ReportProgress(Convert.ToInt32(percentDone), "Burning the disc");
}
else {
_backgroundWorker.ReportProgress(Convert.ToInt32(percentDone), e.Message);
}

GeneralThis code appears to be (partially) broken for the backup of non-root folders.
Member 242473
4:03 2 Jan '10  
Is anyone still following this article and have you also discovered that when burning folders nested deeper than the root level of the drive, this code doesn't work when "Use UI Report" is checked?

First, let me say "Thanks!" to dmihailescu for posting this very good C# example. I am still excited about adapting this code to suit my purposes - specifically to create a series of DVD+R ISO image files from a folder that exceeds the capacity of a single disc. I have been busy shooting lots of HD home movies and now I have to pay the piper. I want to backup files to DVD+R (BluRay, some day) in "native" format (no proprietary archive format) and without any arbitrary file-splitting (though I realize that some splitting for really BIG files may be necessary).

I assume that this will require the addition of some pre-backup analysis and perhaps the creation of multiple instances of ImageRepository objects, but I have not yet gotten that far in my thinking. First, I wanted to be sure that the baseline code works.

While investigating, I first chose a folder that contained over 8 GB of data. The Create File process terminated with a rather obscure/misleading diagnostic message. I soon realized that I should choose a folder that was well within the 4.7 GB limit (and also switch to UDF-only filesystem format).

The folder name that I chose to store in my ISO file was "E:\Video_Projects\2009\20090811_sydney_playing". When running with "Use UI Report" checked, different paths and methods are used to construct the filesystem image. (The filesystem appears to be properly constructed when "Use UI Report" is unchecked.) This results in CreateFSIFile doing some funky stuff that causes the leading subfolder names to be included BELOW the top-level directory. That is, you end up with entries like \\20090811_sydney_playing\Video_Projects\2009\...

If someone (the author?) has already fixed this, could you post your changes? I've been trying to do it myself, but all of the holiday distractions have reduced me to an unfocused blockhead - at least, temporarily. Smile

Thanks,
Mark
GeneralRe: This code appears to be (partially) broken for the backup of non-root folders.
dmihailescu
6:56 4 Jan '10  
Thanks for the appreciation.Rose
I might revisit this article when VS2010 comes along.
If you find what is wrong than please post it here so people can benefit.
Generalopen source in c++
sriman_vema
2:20 5 Dec '09  
Hi any Open source managed code in c++ for creating iso image and editing the iso image.
GeneralHow to add files to the Existing Iso Image
poojary0731
2:06 5 Dec '09  
Thanks for the Good Article about the Iso Image Creation , may i know how to edit the iso image created and also how to mount the iso image and edit the iso image.
GeneralTwo problems
Hasterson
23:08 28 Aug '09  
First when creating ISO making stops on 100% and not say finish and when i try to load it it works...
but secound is that in ISO file there is a problem with sorting directory if you add directory foreach directory in that directory he wiil add the name of first directory or path to and then that directory..
GeneralHow do you create an ISO from a Disk?
yarborg
5:13 31 Dec '08  
I see how to add files, but when I think of making an image of a disk is that what needs to be done? Is there a way to say, makeISO("D:\") or something to specify the CD drive?
QuestionISO of audio cd
giorgiodipietro
6:11 2 Dec '08  
Hi, first you've done a great job! Congratulation.
Now my task: i'm trying to make iso file of an audio cd and the result is not good. The iso file maked is of 65kb and contains the .cda files but not the tracks.
I suppose that i must use IDiscFormat2TrackAtOnce but your void ISOFormatter use IDiscFormat2Data.
What can i do to make ISO files of an audio cd?
Thank
Giorgio from Italy
Questionmultiple folder occurence in source folders: Internal error occured: Removing key from data structure.
ViRiX
13:30 6 Nov '08  
On line 138 of the ISOBuilderForm.cs, I get: Internal error occured: Removing key from data structure.

I've googled, and found: https://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3027523&amp;SiteID=1[^]

This in turn refers to upgrading FreeMediaBlocks: http://msdn.microsoft.com/en-us/library/aa365692(VS.85).aspx[^]

If I try that solution, I get an object reference not set error. Any other ideas as to why this fails? All files combined are little over 2GB.

Update:

I've recreated the files I wanted to pack. on CDCheck.exe, it seems to fail.
This is a 1:1 copy of the file structure and file sizes (written a small tool to replicate the original data with 0-filled files) and rarred them. Unpacked, it's little over 2GB, packed just 25 KB
www.nickkusters.com/fake_fable_dvd.rar[^]

Please let me know if you find out what is causing this.

Update 2:

In your ISOBurner Example, the error is a little more specific: Failed on UI thread: multiple folder occurence in source folders: Internal error occured: Removing key from data structure.

The thing is; I can't see what's wrong with it. I've used ImgBurn to create an image of the exact same files without a hitch...
AnswerRe: multiple folder occurence in source folders: Internal error occured: Removing key from data structure.
dmihailescu
14:13 6 Nov '08  
This is a duplicate response:
If you have 2GB of data make sure that the disk type allows that much data, a CD is too small.
The second error points to a bug in my code or Microsoft's IMAPI. Sigh
It says that you are trying to add a folder that already have been added.
Try changing the folder name that repeats itself.
GeneralThanks
Muhammed Sahin
0:55 31 May '08  
Useful sample for me.


Last Updated 30 May 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010