Garrett - MSFT

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 =
"c:\Sample.iso"

' 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)
recorder.InitializeDiscRecorder(uniqueID)

' 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
    dataWriter.write(stream)
    WScript.Echo "Burn complete!"
ELSE
    WScript.Echo "[ERROR] please insert blank media!"
END IF

WScript.Echo "Exiting..."

edit: updated blank check to use proper function




Re: Optical Platform Discussion Writing an ISO image to disc

Chris P.

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.

Thanks,
Chris






Re: Optical Platform Discussion Writing an ISO image to disc

Garrett - MSFT

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! :)

thanks

Garrett Jacobson
SDE, Optical Platform Group

edit: included suggestion to use progress events






Re: Optical Platform Discussion Writing an ISO image to disc

Brent Scriver

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.





Re: Optical Platform Discussion Writing an ISO image to disc

Brent Scriver

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_RECORDER_NOT_SUPPORTED = 7;

const int ERR_DRIVE_DISK_NOT_SUPPORTED = 8;

const int ERR_DRIVE_NOT_FOUND = 9;

#endregion

/// <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);

}

[STAThread]

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;

break;

}

}

}

// 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);

Result = ERR_DRIVE_NOT_FOUND;

goto Exit;

}

// Ensure the drive supports writing.

List<IMAPI_FEATURE_PAGE_TYPE> Features = new List<IMAPI_FEATURE_PAGE_TYPE>();

foreach (IMAPI_FEATURE_PAGE_TYPE IFPT in DiscRecorder.SupportedFeaturePages)

{

Features.Add(IFPT);

}

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

if (!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_BD_WRITE) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_CD_RW_MEDIA_WRITE_SUPPORT) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_CDRW_CAV_WRITE) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_DOUBLE_DENSITY_CD_R_WRITE) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_DOUBLE_DENSITY_CD_RW_WRITE) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_DVD_DASH_WRITE) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_DVD_PLUS_R) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_DVD_PLUS_R_DUAL_LAYER) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_DVD_PLUS_RW) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_HD_DVD_WRITE) &&

!Features.Contains(IMAPI_FEATURE_PAGE_TYPE.IMAPI_FEATURE_PAGE_TYPE_CD_MASTERING))

{

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

Result = ERR_DRIVE_NOT_SUPPORTED;

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.

try

{

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

Result = ERR_DRIVE_FAILED_ACQUIRE;

goto Exit;

}

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

DiscRecorder.DisableMcn();

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

try

{

DiscFormatData.Write(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;

}

Exit:

// Cleanup

if (DiscRecorder != null && HasExclusive)

{

DiscRecorder.EnableMcn();

DiscRecorder.ReleaseExclusiveAccess();

DiscRecorder.EjectMedia();

}

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.

try

{

IDiscFormat2DataEventArgs progress = (IDiscFormat2DataEventArgs)prog;

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

switch (progress.CurrentAction)

{

case IMAPI2.IMAPI_FORMAT2_DATA_WRITE_ACTION.IMAPI_FORMAT2_DATA_WRITE_ACTION_VALIDATING_MEDIA:

strTimeStatus = "Validating media " + strTimeStatus;

break;

case IMAPI2.IMAPI_FORMAT2_DATA_WRITE_ACTION.IMAPI_FORMAT2_DATA_WRITE_ACTION_FORMATTING_MEDIA:

strTimeStatus = "Formatting media " + strTimeStatus;

break;

case IMAPI2.IMAPI_FORMAT2_DATA_WRITE_ACTION.IMAPI_FORMAT2_DATA_WRITE_ACTION_INITIALIZING_HARDWARE:

strTimeStatus = "Initializing Hardware " + strTimeStatus;

break;

case IMAPI2.IMAPI_FORMAT2_DATA_WRITE_ACTION.IMAPI_FORMAT2_DATA_WRITE_ACTION_CALIBRATING_POWER:

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

break;

case IMAPI2.IMAPI_FORMAT2_DATA_WRITE_ACTION.IMAPI_FORMAT2_DATA_WRITE_ACTION_WRITING_DATA:

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;

break;

case IMAPI2.IMAPI_FORMAT2_DATA_WRITE_ACTION.IMAPI_FORMAT2_DATA_WRITE_ACTION_FINALIZATION:

strTimeStatus = "Finishing the writing " + strTimeStatus;

break;

case IMAPI2.IMAPI_FORMAT2_DATA_WRITE_ACTION.IMAPI_FORMAT2_DATA_WRITE_ACTION_COMPLETED:

strTimeStatus = "Completed the burn.";

break;

default:

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

break;

};

Program.Log(strTimeStatus);

}

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

return ERR_DRIVE_RECORDER_NOT_SUPPORTED;

}

if (!DFD.IsCurrentMediaSupported(DiscRecorder))

{

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

return ERR_DRIVE_DISK_NOT_SUPPORTED;

}

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

sb.Append(((IMAPI_FORMAT2_DATA_MEDIA_STATE)i).ToString());

}

}

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;

}

}

}





Re: Optical Platform Discussion Writing an ISO image to disc

sdfsdfhg

 Brent Scriver wrote:

DR.InitializeDiscRecorder(DiscMaster);



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

DR.InitializeDiscRecorder(DiscMasterIdea);

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






Re: Optical Platform Discussion Writing an ISO image to disc

Garrett - MSFT

sdfsdfhg:

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

Brent:

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






Re: Optical Platform Discussion Writing an ISO image to disc

Brent Scriver

 

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.





Re: Optical Platform Discussion Writing an ISO image to disc

Garrett - MSFT

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!

thanks,

Garrett Jacobson
SDE, Optical Platform Group






Re: Optical Platform Discussion Writing an ISO image to disc

SebMouren

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.

-Sebastien Mouren





Re: Optical Platform Discussion Writing an ISO image to disc

Garrett - MSFT

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

thanks,

Garrett Jacobson

SDE, Optical Platform Group