Writing an ISO image to disc RRS feed

  • Question

  • Hello everyone!

    Thanks to everyone who participated in the Optical Beta chat today!

    I'm trying to get a better, full-feature sample that we can put up on the forums, but for now here is a sample derived from our WinHEC slides that burn an ISO image to a disc.  The only missing piece is creating a stream over the ISO file.  I cannot get a piece of code that I can release for this in VBScript yet, although a good start is SHCreateStreamOnFile for C++ development.  Even though it does not work without this one function, this sample still shows the necessary steps to burn an ISO image.  Although this is officially unsupported code, I'd be glad to answer any questions about the IMAPIv2 functionality it exhibits...

    ' Recorder index and ISO path
    recorderIndex = 0
    isoPath =

    ' Create and initialize IDiscRecorder2 from IDiscMaster
    WScript.Echo "Creating and initializing disc recorder..."
    SET g_DiscMaster = WScript.CreateObject("IMAPI2.MsftDiscMaster2")
    SET recorder = WScript.CreateObject("IMAPI2.MsftDiscRecorder2")
    uniqueID = g_DiscMaster.Item(recorderIndex)

    ' Create a stream filled with the ISO file contents
    WScript.Echo "Creating stream on ISO file..."
    SET stream = fnCreateStreamFromFile(isoPath)

    ' Create IDiscFormat2Data and attach to IDiscRecorder2
    WScript.Echo "Writing..."
    SET dataWriter = WScript.CreateObject("IMAPI2.MsftDiscFormat2Data")
    dataWriter.recorder = recorder
    dataWriter.ClientName =
    "VB ISO Burner"

    ' Check if disc is blank and burn (or exit)
    IF (dataWriter.MediaHeuristicallyBlank) THEN
        WScript.Echo "Burn complete!"
        WScript.Echo "[ERROR] please insert blank media!"
    END IF

    WScript.Echo "Exiting..."

    edit: updated blank check to use proper function

    Friday, September 1, 2006 7:40 PM


  • Hi Garret,

    So if I rewrite this as a C++ sample it should work?  It seems like it should as IDiscFormat2Data::Write takes an IStream* and SHCreateStreamOnFile() returns an IStream*.

    If so I'll try it out and make a Shell plug-in for right click burning ISO files.


    Friday, September 1, 2006 8:06 PM

All replies

  • Hi Garret,

    So if I rewrite this as a C++ sample it should work?  It seems like it should as IDiscFormat2Data::Write takes an IStream* and SHCreateStreamOnFile() returns an IStream*.

    If so I'll try it out and make a Shell plug-in for right click burning ISO files.


    Friday, September 1, 2006 8:06 PM
  • Hey Chris,

    Yes, this should work in a C++ sample if you use SHCreateStreamOnFile.  IDiscFormat2Data::Write will write out the contents of any IStream to the disc, so any function that will get the contents of the ISO file into stream form will work.

    Although we were unable to get it as part of Vista, our team has also written a Shell plug-in that we're trying to get released as a PowerToy or something like that.  Until we can get that finalized though, it will be up the community to help eachother out and produce a good ISO burning solution...

    In fact, if you'd like to implement a more full-featured solution than just using the sample code provided here, you can take advantage of the progress events reported by IDiscFormat2Data to present a progress display to the user as well as implementing cancellation logic (remember: cancelled burns can leave discs in a bad state so don't trust the contents of a disc immediately after a cancelled burn).  You can reference the IMAPIv2 documentation on MSDN (or in the SDK, where we have more samples) at: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/imapi/imapi/imapi_interfaces.asp

    If you have any further questions, please let me know and I'd be happy to assist in any way i can! :)


    Garrett Jacobson
    SDE, Optical Platform Group

    edit: included suggestion to use progress events

    Friday, September 1, 2006 9:13 PM
  • Hmmm, I'm trying to do this in C#.  I have almost everything working except one step: getting the updates during the write.

    I'm using Vista build 5728 and Visual Studio 2005 (I do not have the Windows SDK installed for Vista).  I've added the interface through the COM tab and everything else works except this.

    Essentially I've just appended an event handler with the declaration of:

    public void DiscFormatData_Update(object obj, object prog)

    And i've even added output to the event to generate something at least for me to look at, set a breakpoint, everything.  Regardless it is not being called.  I've tried making it static, assigning it to the Update method of MsftDiscFormat2DataClass object instead of appending (which failed to compile), and a couple of other variants.  Even went to the effort of seeing if threading was somehow an issue but no evidence appeared that that was the case.

    If posting the full source would be useful, I certainly can do so.

    Thursday, September 28, 2006 2:48 AM
  • Well, to be thorough, and to ease getting a quick answer, here's the full source.  Nothing special.  Just a cl tool to write iso images to CDs/DVDs since Vista doesn't have something built in and Nero doesn't work on a clean install (last time I checked).  Recommend cutting and pasting to a C# file for formatting.  Paste wasn't as good as I had hoped.


    using System;

    using System.Collections.Generic;

    using System.Text;

    using IMAPI2;

    using System.Runtime.InteropServices;

    using System.Threading;

    namespace ISOBurn


    class Program


    #region Error codes

    const int ERR_INVALID_ARGS = 1;

    const int ERR_FILE_NOT_FOUND = 2;

    const int ERR_DRIVE_NOT_SUPPORTED = 3;

    const int ERR_DRIVE_IN_USE = 4;

    const int ERR_DRIVE_FAILED_ACQUIRE = 5;

    const int ERR_DISK_NOT_BLANK = 6;



    const int ERR_DRIVE_NOT_FOUND = 9;


    /// <summary>

    /// Creates an IStream from the given file

    /// </summary>

    /// <param name="pszFile">File to create a stream from</param>

    /// <param name="grfMode">Flags on how to open the file</param>

    /// <param name="ppstm">IStream object returned</param>

    /// <returns>An HRESULT indicating pass or fail</returns>

    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]

    static extern int SHCreateStreamOnFile(string pszFile, uint grfMode, out IMAPI2.IStream ppstm);

    /// <summary>

    /// Wrapper for logging in case I need to make sure it responds faster than a write to a console window

    /// </summary>

    /// <param name="LogLine">Format string to log</param>

    /// <param name="list">Parameters to the format string</param>

    public static void Log(string LogLine, params object [] list)


    Console.WriteLine(LogLine, list);



    static int Main(string[] args)


    int Result = 0;

    bool HasExclusive = false;

    // First and second arguments

    string Drive = null, ISOImage = null;

    MsftDiscMaster2Class DiscMaster = new MsftDiscMaster2Class();

    MsftDiscRecorder2Class DiscRecorder = null;

    DDiscFormat2DataEvents_UpdateEventHandler EventHandler = new DDiscFormat2DataEvents_UpdateEventHandler(DiscFormatData_Update);

    // Check arguments.

    if (args.Length < 2)


    Log("Usage: ISOBurn <DrivePath> <ISOImage>");

    Log("\tISOBurn e: d:\\summerpics.iso");

    Result = ERR_INVALID_ARGS;

    goto Exit;


    ISOImage = args[1];

    // If the file doesn't exist, waste no more time

    if (!System.IO.File.Exists(args[1]))


    Console.WriteLine("File not found: {0}", ISOImage);

    Result = ERR_FILE_NOT_FOUND;

    goto Exit;


    // Find the corresponding disc recorder for the drive

    for (int i = 0; i < DiscMaster.Count; ++i)


    MsftDiscRecorder2Class DR = new MsftDiscRecorder2Class();

    DR.InitializeDiscRecorder(DiscMaster[ i ]);

    foreach (string s in DR.VolumePathNames)


    // Indicate the drive letter the system is using and set the disc recorder

    if (s.StartsWith(args[0], StringComparison.CurrentCultureIgnoreCase))


    Log("Using drive {0} for burning from ID {1}", s, DiscMaster[ i ]);

    Drive = s;

    DiscRecorder = DR;





    // If we didn't set DiscRecorder, then no drive was found

    if (DiscRecorder == null)


    Log("The drive {0} to burn to was not found.", Drive);


    goto Exit;


    // Ensure the drive supports writing.


    foreach (IMAPI_FEATURE_PAGE_TYPE IFPT in DiscRecorder.SupportedFeaturePages)




    // Is there a better way to do this? Doesn't look like a bitfield based on the enums (saddened)













    Log("The drive {0} does not appear to support writing.", Drive);


    goto Exit;


    // Check to see if another application is already using the media and if so bail

    MsftDiscFormat2DataClass DiscFormatData = null;

    if (DiscRecorder.ExclusiveAccessOwner != null && DiscRecorder.ExclusiveAccessOwner.Length != 0)


    Log("The drive {0} is in use by the application: {1}", Drive, DiscRecorder.ExclusiveAccessOwner);

    Result = ERR_DRIVE_IN_USE;

    goto Exit;


    // Try to get exclusive access. This is important. In Vista 5728, Media Player causes this to return E_FAIL

    // even if it is just playing music in the background.



    DiscRecorder.AcquireExclusiveAccess(false, "ISOBurn Commandline Tool");


    catch (System.Exception e)


    Log("Failed to acquire exclusive access to the burner: Message: {0}\nStack Trace: {1}", e.Message, e.StackTrace);


    goto Exit;


    // Disable media change notifications so we don't have anyone else being notified that I'm doing stuff on the drive


    // Indicate we have exclusive access.

    HasExclusive = true;

    // Get the disk format which will hopefully let us know if we can write to the disk safely.

    Result = GetDiskFormatData(DiscRecorder, Drive, out DiscFormatData);

    if (Result != 0)


    goto Exit;


    // I would like to get the amount of free space on the media and compare that against the file size, but I'm not sure

    // if the file size of the ISO represents the raw sectors that would be written or whether it might be more tightly

    // packed. Also there might be additional sectors written on the disk to identify the image, etc that isn't

    // represented in the ISO. These are details I don't know to make this a little bit more robust.

    // Get the image to write

    IMAPI2.IStream Stream = null;

    int Res = SHCreateStreamOnFile(ISOImage, 0x20, out Stream);

    if (Res < 0)


    Log("Opening the source ISO image {0} failed with error: {1}", ISOImage, Res);

    Result = Res;

    goto Exit;


    // Set the client name

    DiscFormatData.ClientName = "ISOBurn Commandline Tool";

    // Add the event handler *WHICH CURRENTLY DOES GENERATE EVENTS*

    DiscFormatData.Update += EventHandler;

    Log("Disk write speed is {0} sectors/second", DiscFormatData.CurrentWriteSpeed);

    // Write the stream




    Log("Burn complete of {0} to {1}!", ISOImage, Drive);


    catch (System.Exception e)


    Log("Burn Failed: {0}\n{1}", e.Message, e.StackTrace.ToString());

    goto Exit;



    // Cleanup

    if (DiscRecorder != null && HasExclusive)






    return Result;


    /// <summary>

    /// Outputs an updated status of the write. Currently not being called--don't know why.

    /// Documentation at: http://windowssdk.msdn.microsoft.com/en-us/library/ms689023.aspx

    /// </summary>

    /// <param name="obj">An IDiscFormat2Data interface</param>

    /// <param name="prog">An IDiscFormat2DataEventArgs interface</param>

    static void DiscFormatData_Update(object obj, object prog)


    // Update the status progress of the write.



    IDiscFormat2DataEventArgs progress = (IDiscFormat2DataEventArgs)prog;

    string strTimeStatus = "Time: " + progress.ElapsedTime + " / " + progress.TotalTime;

    switch (progress.CurrentAction)



    strTimeStatus = "Validating media " + strTimeStatus;



    strTimeStatus = "Formatting media " + strTimeStatus;



    strTimeStatus = "Initializing Hardware " + strTimeStatus;



    strTimeStatus = "Calibrating Power (OPC) " + strTimeStatus;



    long totalSectors, writtenSectors;

    double percentDone;

    totalSectors = progress.SectorCount;

    writtenSectors = progress.LastWrittenLba - progress.StartLba;

    percentDone = writtenSectors * 100;

    percentDone /= totalSectors;

    strTimeStatus = "Progress: " + percentDone.ToString("0.00") + "% " + strTimeStatus;



    strTimeStatus = "Finishing the writing " + strTimeStatus;



    strTimeStatus = "Completed the burn.";



    strTimeStatus = "Unknown action: " + progress.CurrentAction;





    catch (System.Exception e)


    Program.Log("Update Exception: Message: {0}\nStack Trace: {1}", e.Message, e.StackTrace.ToString());



    /// <summary>

    /// Determines if the format of the media is ok to write and a few other details. Does some informational output too.

    /// </summary>

    /// <param name="DiscRecorder">DiscRecorder being written with</param>

    /// <param name="Drive">Drive for the DiscRecorder</param>

    /// <param name="DiscFormatData">The MsftDiscFormat2DataClass that will be returned if it is ok to write</param>

    /// <returns>Zero if successful, non-zero otherwise.</returns>

    static int GetDiskFormatData(MsftDiscRecorder2Class DiscRecorder, string Drive, out MsftDiscFormat2DataClass DiscFormatData)


    MsftDiscFormat2DataClass DFD = new MsftDiscFormat2DataClass();

    DFD.Recorder = DiscRecorder;

    DiscFormatData = null;

    if (!DFD.IsRecorderSupported(DiscRecorder))


    Log("The recorder for drive {0} is not supported.", Drive);



    if (!DFD.IsCurrentMediaSupported(DiscRecorder))


    Log("The media for drive {0} is not supported.", Drive);



    // I'm not sure exactly what the difference is between heuristically blank and blank is unless it means that there is room

    // left to be written to and it isn't finalised?

    if (!DFD.MediaHeuristicallyBlank)


    Log("The disk in drive {0} isn't \"heuristically\" blank. No media was written.", Drive);

    return ERR_DISK_NOT_BLANK;


    // Get the media status properties for the media to for diagnostic purposes

    StringBuilder sb = new StringBuilder();

    bool FirstWritten = false;

    sb.Append("Media status for drive ").Append(Drive).Append(" is ");

    for (int i = 1; i <= (int) DFD.CurrentMediaStatus; i <<= 1)


    if ((((int) DFD.CurrentMediaStatus) & i) == i)


    if (FirstWritten)


    sb.Append(" | ");


    FirstWritten = true;




    Log("{0}", sb.ToString());

    // And log the media type for diagnostics

    Log("Media type for drive {0} is {1}", Drive, DFD.CurrentPhysicalMediaType.ToString());

    // Return the result since we are good if we got here.

    DiscFormatData = DFD;

    return 0;




    Thursday, September 28, 2006 3:21 AM
  •  Brent Scriver wrote:


    Fantastic, I've been wondering how it might be possible to write to CDR/DVDR devices using .net, this is a great help, however I'm having a problem with the bits of code that are being converted to emoticons.

    Does anyone know what the lightbulb emoticon actually translates to in text form?

    Edit: When I cut and paste the above code into the IDE it produces the following


    Edit2: I should try actually reading the code. Try replacing 'Idea' with '[ i ]' (minus the spaces), and hey presto, all is good.

    Wednesday, January 10, 2007 7:08 PM
  • sdfsdfhg:

    yup, you've got it, he's just referencing into the object as an array :)



    sorry for the delay, somehow I never caught your reply till now.  Anyways, I'll need some more time to look into the events issue, but I can offer a couple quick suggestions/pointers:

    1. you can use IDiscFormat2Data::IsRecorderSupported() to determine if the recorder is a supported burner.  Additionally, you can use IDiscFormat2Data::IsCurrentMediaSupported to determine if it's a burner AND it supports writing the current piece of media

    2. the ISO file should be the same size as it will be on the disc, so comparing sizes should be just fine

    I'll get back to you with more information as I get some time to look into this!  Again, sorry for the delay...


    -Garrett Jacobson
    SDE, Optical Platform Group


    Wednesday, January 10, 2007 7:50 PM

    Unfortunately, I no longer have any flavour of Vista running.  I ran into issues with the nvidia drivers with the final release rendering games unplayable (it doesn't like running at 2560x1600).  Also I needed Virtual PC to work in case my server died so I can keep web sites going.  So I'm not in a state to investigate this further at the moment (unless this would work in XP as well?).

    Also, one other bug I hit with this that I logged through the Vista bug reporting system was that if Windows Media Player (or Virtual PC) was up the code would get past the check of whether the drive was in use but then fail to acquire exclusive access.  It looks like the conditions it checks to see if the drive is in use is a subset of what acquiring the drive would do.

    Hmm, I actually do hit both of those functions (drive & media supported) in the function at the bottom: GetDiskFormatData.

    If it helps any, the drive I have is a Pioneer DVR-110D ROM v. 1.41.

    Thursday, January 11, 2007 3:44 PM
  • Ahhh, sorry I didn't get this to you in time.  IMAPIv2 is not currently available on XP though, so this won't work yet.

    As for the bug, you are correct: there was a bug in some versions of Vista (that has been fixed) where if you tried to acquire exclusive access without force=TRUE (which your code uses force=FALSE), it would often fail due to an issue with the cdrom driver.  This has been fixed and I believe should be working fine in RTM bits :)

    I noticed you used the functions, I was just recommending using them in the section where you commented:
    // Is there a better way to do this? Doesn't look like a bitfield based on the enums (saddened)

    Since the best way to determine that is simply by using those two functions.  Just FYI, the reason they are not a bitfield is because these feature numbers are not IMAPIv2 specific numbers but an MMC specification (or Mt. Fuji) defined constant per feature.  We just defined an enumeration for them so that people didn't have to refer to the specification for the correct number...

    Please let me know if you have any further questions!


    Garrett Jacobson
    SDE, Optical Platform Group

    Thursday, January 18, 2007 7:04 PM
  • Hello Gartett,

    The script center propose there to create an IStream from script using the ADODB.Stream object.

    Do you recommend this approach?

    May you describe to us the specific properties the IMAPI2.Stream object requires?


    I identified a problem with Script Center approach: the stream is created in memory. It makes this trick unusable.


    -Sébastien Mouren

    Wednesday, July 18, 2007 10:10 AM
  • Hello SebMouren,


    So, this is the reason I wrote the sample but left out the Stream part for ISO burning ;-)

    Currently, that is the only method I know of to get it to work in a simple scripting environment.  It has two downsides:


    1. As you mentioned, it creates the entire stream in memory.  This is infeasible for large ISO files.
    2. That object has many security difficulties in the past which has led to it sometimes being disabled on systems, possibly making it a solution that won't work everywhere.

    Because of those things, I have tried not to recommend it in the past.  That said, I have not yet found a simple way to do it.


    If you were looking into other solutions, you should know that an IMAPI2.Stream object really just needs to be an object which exposes the IStream interface.  However, we don't actually need ALL the functionality.  From my initial investigation, it appears the minimal set of functionality needed to support burning with IMAPIv2 from an IStream object is:

    1. IStream::Read
    2. IStream:Tongue Tiedeek
    3. IStream:Tongue Tiedtat

    Please let me know if you have any further questions or if you find a better solution!  I'm sure the community would love a good answer to this one....




    Garrett Jacobson

    SDE, Optical Platform Group





    Wednesday, July 18, 2007 4:14 PM