text: /*++
  
  Copyright (c) 2005  Microsoft Corporation
  
  Module Name:
  
      imapi2sample.c
  
  Abstract:
  
      A user mode console app that demenstrates the IMAPIv2 interfaces.
  
  Environment:
  
      User mode only
  
  Revision History:
  
      01-01-05 : Created
  
  --*/
  #include "imapi2sample.h"
  #include <ntverp.h>
  
  #define BOOLEAN_STRING(_x) ((_x)?"TRUE":"FALSE")
  
  VOID
  PrintHelp(
      WCHAR * selfName
      )
  {
      printf("%S: %s\n"
             "Usage:\n"           
             "%S -list\n"
             "%S -write <dir> [-multi] [-close] [-drive <#>] [-boot <file>]\n"
             "%S -audio <dir> [-close] [-drive <#>]\n"
             "%S -raw <dir> [-close] [-drive <#>]\n"
             "%S -image <file>[-close] [-drive <#>] [-bufe | -bufd]\n"
             "%S -erase [-drive <#>]\n"
             "\n"
             "\tlist      -- list the available writers and their index.\n"
             "\terase     -- quick erases the chosen recorder.\n"
             "\tfullerase -- full erases the chosen recorder.\n"
             "\twrite     -- Writes a directory to the disc.\n"
             "\t   <dir>  -- Directory to be written.\n"
             "\t   [-SAO] -- Use Cue Sheet recording.\n"
             "\t   [-close] -- Close disc (not appendable).\n"
             "\t   [-drive <#>] -- choose the given recorder index.\n"
             "\t   [-iso, -udf, -joliet] -- specify the file system to write.\n"
             "\t   [-multi] -- Add an additional session to disc.\n"
             "\t   [-boot <file>] -- Create a boot disk.  File is a boot image.\n"
             "\teject     -- Eject the CD tray\n"
             "\tinject    -- Close the CD tray\n",
             selfName, VER_PRODUCTVERSION_STR,
             selfName,selfName,selfName,selfName,selfName,selfName
             );
      return;
  }
  
  BOOLEAN
  ParseCommandLine(
      IN DWORD Count,
      IN WCHAR * Arguments[],
      OUT PPROGRAM_OPTIONS Options
      )
  {
      BOOLEAN goodEnough = FALSE;
      // initialize with defaults
      RtlZeroMemory( Options, sizeof(PROGRAM_OPTIONS) );
  
      for ( DWORD i = 0; i < Count; i++ )
      {
          if ( (Arguments[i][0] == '/') || (Arguments[i][0] == '-') )
          {
              BOOLEAN validArgument = FALSE;
              Arguments[i][0] = '-'; // minimize checks below
  
              //
              // If the first character of the argument is a - or a / then
              // treat it as an option.
              //
              if ( _wcsnicmp(Arguments[i], (L"-write"), strlen("-write")) == 0 )
              {
                  Options->Write = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-image"), strlen("-image")) == 0 )
              {
                  Options->Image = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-audio"), strlen("-audio")) == 0 )
              {
                  Options->Audio = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-raw"), strlen("-raw")) == 0 )
              {
                  Options->Raw = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-close"), strlen("-close")) == 0 )
              {
                  Options->CloseDisc = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-multi"), strlen("-multi")) == 0 )
              {
                  Options->Multi = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-iso"), strlen("-iso")) == 0 )
              {
                  Options->Iso = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-joliet"), strlen("-joliet")) == 0 )
              {
                  Options->Joliet = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-udf"), strlen("-udf")) == 0 )
              {
                  Options->UDF = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-free"), strlen("-free")) == 0 )
              {
                  Options->FreeSpace = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-inject"), strlen("-inject")) == 0 )
              {
                  Options->Close = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-eject"), strlen("-eject")) == 0 )
              {
                  Options->Eject = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-drive"), strlen("-drive")) == 0 )
              {                
                  // requires second argument
                  if ( i+1 < Count )
                  {
                      i++; // advance argument index
                      ULONG tmp = 666;
                      if ( (swscanf_s( Arguments[i], (L"%d"), &tmp ) == 1) &&
                           (tmp != 666) )
                      {
                          // Let's do this zero based for now
                          Options->WriterIndex = tmp;
                          validArgument = TRUE;
                      }
                  }
  
                  if ( !validArgument )
                  {
                      printf("Need a second argument after drive,"
                             " which is the one-based index to the\n"
                             "writer to use in decimal format.  To"
                             "get a list of available drives and"
                             "their indexes, use \"-list\" option\n"
                             );
                  }
              }
              else if ( _wcsnicmp(Arguments[i], (L"-boot"), strlen("-boot")) == 0 )
              {                
                  // requires second argument
                  if ( i+1 < Count )
                  {
                      i++; // advance argument index
                      ULONG tmp = 666;
                      if ( Arguments[i] != NULL )
                      {
                          // Let's do this zero based for now
                          Options->BootFileName = Arguments[i];
                          validArgument = TRUE;
                      }
                  }
  
                  if ( !validArgument )
                  {
                      printf("Need a second argument after boot,"
                             " which is the boot file the\n"
                             "writer will use\n"
                             );
                  }
              }
              else if ( _wcsnicmp(Arguments[i], (L"-vol"), strlen("-vol")) == 0 )
              {                
                  // requires second argument
                  if ( i+1 < Count )
                  {
                      i++; // advance argument index
                      ULONG tmp = 666;
                      if ( Arguments[i] != NULL )
                      {
                          // Let's do this zero based for now
                          Options->VolumeName = Arguments[i];
                          validArgument = TRUE;
                      }
                  }
  
                  if ( !validArgument )
                  {
                      printf("Need a second argument after vol,"
                             " which is the volume name for\n"
                             "the disc\n"
                             );
                  }
              }
              else if ( _wcsnicmp(Arguments[i], (L"-list"), strlen("-list")) == 0 )
              {
                  Options->ListWriters = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-erase"), strlen("-erase")) == 0 )
              {
                  Options->Erase = TRUE;
                  Options->FullErase = FALSE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-fullerase"), strlen("-fullerase")) == 0 )
              {
                  Options->Erase = TRUE;
                  Options->FullErase = TRUE;
                  validArgument = TRUE;
              }
              else if ( _wcsnicmp(Arguments[i], (L"-?"), strlen("-?")) == 0 )
              {
                  printf("Requesting help\n");
              }
              else
              {
                  printf("Unknown option -- %S\n", Arguments[i]);
              }
  
              if(!validArgument)
              {
                  return FALSE;
              }
          }
          else if ( Options->FileName == NULL )
          {
              //
              // The first non-flag argument is the ISO to write name.
              //
  
              Options->FileName = Arguments[i];
  
          }
          else
          {
  
              //
              // Too many non-flag arguments provided.  This must be an error.
              //
  
              printf("Error: extra argument %S not expected\n", Arguments[i]);
              return FALSE;
          }
      }
  
      //
      // Validate the command-line arguments.
      //
      if ( Options->ListWriters )
      {
          // listing the Writers stands alone
          if ( !(Options->Write || Options->Erase ) )
          {
              goodEnough = TRUE;
          }
          else
          {
              printf("Error: Listing writers must be used alone\n");
          }
      }
      else if ( Options->Write )
      {
          // Write allows erase, but not self-test
          // Write requires at least a filename
          if ( Options->FileName != NULL )
          {
              goodEnough = TRUE;
          }
          else
          {
              printf("Error: Write requires directory\n");
          }
  
          // validate erase options?
      }
      else if ( Options->Image )
      {
          // Write allows erase, but not self-test
          // Write requires at least a filename
          if ( Options->FileName != NULL )
          {
              goodEnough = TRUE;
          }
          else
          {
              printf("Error: Image requires filename\n");
          }
  
      }
      else if ( Options->Audio )
      {
          // Write allows erase, but not self-test
          // Write requires at least a filename
          if ( Options->FileName != NULL )
          {
              goodEnough = TRUE;
          }
          else
          {
              printf("Error: Audio requires directory\n");
          }
  
      }
      
      else if ( Options->Raw )
      {
          // Write allows erase, but not self-test
          // Write requires at least a filename
          if ( Options->FileName != NULL )
          {
              goodEnough = TRUE;
          }
          else
          {
              printf("Error: Raw requires directory\n");
          }
  
      }
  
      else if ( Options->Erase )
      {
          // validate erase options?
          goodEnough = TRUE;
      }
  
  
      // These are not stand alone options.
      //if ( Options->CloseDisc )
      //{
      //    //printf("Option 'DiscOpen' is not yet implemented\n");
      //    goodEnough = TRUE;
      //}
      //if ( Options->Multi )
      //{
      //    goodEnough = TRUE;
      //}
  
      if ( Options->FreeSpace )
      {
          goodEnough = TRUE;
      }
      if ( Options->Eject )
      {
          goodEnough = TRUE;
      }
      if ( Options->Close )
      {
          goodEnough = TRUE;
      }
  
      if ( !goodEnough )
      {
          RtlZeroMemory( Options, sizeof(PROGRAM_OPTIONS) );
          return FALSE;
      }
  
      return TRUE;
  }
  
  //*********************************************************************
  //* FUNCTION: GetSecondsElapsed
  //*          
  //* PURPOSE: 
  //*********************************************************************
   DWORD
   GetSecondsElapsed(
      SYSTEMTIME * StartTime,
      SYSTEMTIME * EndTime)
      {
      FILETIME Start,End;
      unsigned __int64 Start64=0, End64=0, Elapsed64=0; 
      
      
      //
      //--- Convert System time
      //
      SystemTimeToFileTime(StartTime,&Start);
      SystemTimeToFileTime(EndTime,&End);
  
       
      //
      //---- Convert start and end file 
      //---- time to 2  64 bit usigned integers
      //
      ((LPDWORD)(&Start64))[1] = Start.dwHighDateTime;
      ((LPDWORD)(&Start64))[0] = Start.dwLowDateTime;
    
      ((LPDWORD)(&End64))[1] = End.dwHighDateTime;
      ((LPDWORD)(&End64))[0] = End.dwLowDateTime;
  
     
      //
      //--- Calc elpased time
      //
      Elapsed64 = End64 - Start64;
      
      //
      //---- Get micro seconds elpased
      //
      Elapsed64 /= 10;
  
      //
      //--- Get milly seconds elpased
      //
      Elapsed64 /= 1000;
  
      //
      //--- Get Secconds elpased
      //
      Elapsed64 /= 1000;
  
      //
      //--- Return the LowDateTime of seconds elapsed
      //--- This will be good enough for ~136 years elapsed
      //
      return(((LPDWORD)(&Elapsed64))[0]);
      }
  
  //*********************************************************************
  //* FUNCTION:CalcElapsedTime
  //*          
  //* PURPOSE: 
  //*********************************************************************
  #define SECONDS_IN_A_DAY     ((DWORD)(SECONDS_IN_A_HOUR*24))
  #define SECONDS_IN_A_HOUR    ((DWORD)(SECONDS_IN_A_MINUTE*60))
  #define SECONDS_IN_A_MINUTE  ((DWORD)(60))
  void 
  CalcElapsedTime(
     SYSTEMTIME * StartTime,
     SYSTEMTIME * FinishTime,
     SYSTEMTIME * ElapsedTime)
     {
     DWORD SecondsElapsed;
     
     memset(ElapsedTime,0,sizeof(SYSTEMTIME));
     
     SecondsElapsed = GetSecondsElapsed(
           StartTime, FinishTime);
  
  
     if(SecondsElapsed >= SECONDS_IN_A_DAY)
        {
        ElapsedTime->wDay = (WORD) (SecondsElapsed / SECONDS_IN_A_DAY);
        SecondsElapsed -= (ElapsedTime->wDay*SECONDS_IN_A_DAY);
        }
     
     
     if(SecondsElapsed >= SECONDS_IN_A_HOUR)
        {
        ElapsedTime->wHour  = (WORD) (SecondsElapsed / SECONDS_IN_A_HOUR);
        SecondsElapsed -= (ElapsedTime->wHour * SECONDS_IN_A_HOUR);
        }
     
     if(SecondsElapsed >= SECONDS_IN_A_MINUTE)
        {
        ElapsedTime->wMinute = (WORD) (SecondsElapsed / SECONDS_IN_A_MINUTE);
        SecondsElapsed -= (ElapsedTime->wMinute * SECONDS_IN_A_MINUTE);
        }
     
     
     ElapsedTime->wSecond = (WORD) SecondsElapsed;
  
     }
  
  
  // using a simple array due to consecutive zero-based values in this enum
  CHAR * g_MediaTypeStrings[] = {
      "IMAPI_MEDIA_TYPE_UNKNOWN",
      "IMAPI_MEDIA_TYPE_CDROM",
      "IMAPI_MEDIA_TYPE_CDR",
      "IMAPI_MEDIA_TYPE_CDRW",
      "IMAPI_MEDIA_TYPE_DVDROM",
      "IMAPI_MEDIA_TYPE_DVDRAM",
      "IMAPI_MEDIA_TYPE_DVDPLUSR",
      "IMAPI_MEDIA_TYPE_DVDPLUSRW",
      "IMAPI_MEDIA_TYPE_DVDPLUSR_DUALLAYER",
      "IMAPI_MEDIA_TYPE_DVDDASHR",
      "IMAPI_MEDIA_TYPE_DVDDASHRW",
      "IMAPI_MEDIA_TYPE_DVDDASHR_DUALLAYER",
      "IMAPI_MEDIA_TYPE_DISK",
      "IMAPI_MEDIA_TYPE_DVDPLUSRW_DUALLAYER",
      "IMAPI_MEDIA_TYPE_HDDVDROM",
      "IMAPI_MEDIA_TYPE_HDDVDR",
      "IMAPI_MEDIA_TYPE_HDDVDRAM",
      "IMAPI_MEDIA_TYPE_BDROM",
      "IMAPI_MEDIA_TYPE_BDR",
      "IMAPI_MEDIA_TYPE_BDRE",
      "IMAPI_MEDIA_TYPE_MAX"
  };
  //typedef enum _STREAM_DATA_SOURCE {
  //    DevZeroStream = 0,
  //    SmallImageStream = 1,
  //    EightGigStream = 2,
  //} STREAM_DATA_SOURCE, *PSTREAM_DATA_SOURCE;
  
  
  // Get a disc recorder given a disc index
  HRESULT GetDiscRecorder(__in ULONG index, __out IDiscRecorder2 ** recorder)
  {
      HRESULT hr = S_OK;
      IDiscMaster2* tmpDiscMaster = NULL;
      BSTR tmpUniqueId;
      IDiscRecorder2* tmpRecorder = NULL;
      
      *recorder = NULL;
  
      // create the disc master object
      if (SUCCEEDED(hr))
      {
          hr = CoCreateInstance(CLSID_MsftDiscMaster2,
                                NULL, CLSCTX_ALL,
                                IID_PPV_ARGS(&tmpDiscMaster)
                                );
          if (FAILED(hr))
          {
              printf("Failed CoCreateInstance\n");
              PrintHR(hr);
          }
      }
  
      // get the unique id string
      if (SUCCEEDED(hr))
      {        
          hr = tmpDiscMaster->get_Item(index, &tmpUniqueId);
          if (FAILED(hr))
          {
              printf("Failed tmpDiscMaster->get_Item\n");
              PrintHR(hr);
          }
      }
  
      // Create a new IDiscRecorder2
      if (SUCCEEDED(hr))
      {
          hr = CoCreateInstance(CLSID_MsftDiscRecorder2,
                                NULL, CLSCTX_ALL,
                                IID_PPV_ARGS(&tmpRecorder)
                                );
          if (FAILED(hr))
          {
              printf("Failed CoCreateInstance\n");
              PrintHR(hr);
          }
      }
      // Initialize it with the provided BSTR
      if (SUCCEEDED(hr))
      {
          hr = tmpRecorder->InitializeDiscRecorder(tmpUniqueId);
          if (FAILED(hr))
          {
              printf("Failed to init disc recorder\n");
              PrintHR(hr);
          }
      }
      // copy to caller or release recorder
      if (SUCCEEDED(hr))
      {
          *recorder = tmpRecorder;
      }
      else
      {
          ReleaseAndNull(tmpRecorder);
      }
      // all other cleanup
      ReleaseAndNull(tmpDiscMaster);
      FreeSysStringAndNull(tmpUniqueId);
      return hr;
  }
  
  HRESULT ListAllRecorders()
  {
      HRESULT hr = S_OK;
      LONG          index = 0;
      IDiscMaster2* tmpDiscMaster = NULL;
  
              // create a disc master object
      if (SUCCEEDED(hr))
      {
          hr = CoCreateInstance(CLSID_MsftDiscMaster2,
                                NULL, CLSCTX_ALL,
                                IID_PPV_ARGS(&tmpDiscMaster)
                                );
          if (FAILED(hr))
          {
              printf("Failed CoCreateInstance\n");
              PrintHR(hr);
          }
      }
      // Get number of recorders
      if (SUCCEEDED(hr))
      {
          hr = tmpDiscMaster->get_Count(&index);
  
          if (FAILED(hr))
          {
              printf("Failed to get count\n");
              PrintHR(hr);
          }
      }
  
      // Print each recorder's ID
      for (LONG i = 0; i < index; i++)
      {
          IDiscRecorder2* discRecorder = NULL;
  
          hr = GetDiscRecorder(i, &discRecorder);
  
          if (SUCCEEDED(hr))
          {
              
              BSTR discId;
              BSTR venId;
    
              // Get the device strings
              if (SUCCEEDED(hr)) { hr = discRecorder->get_VendorId(&venId); }
              if (SUCCEEDED(hr)) { hr = discRecorder->get_ProductId(&discId); }
              if (FAILED(hr))
              {
                  printf("Failed to get ID's\n");
                  PrintHR(hr);
              }                
          
              if (SUCCEEDED(hr))
              {
                  printf("Recorder %d: %ws %ws", i, venId, discId); 
              }
              // Get the mount point
              if (SUCCEEDED(hr))
              {
                  SAFEARRAY * mountPoints = NULL;
                  hr = discRecorder->get_VolumePathNames(&mountPoints);
                  if (FAILED(hr))
                  {
                      printf("Unable to get mount points, failed\n");
                      PrintHR(hr);
                  }
                  else if (mountPoints->rgsabound[0].cElements == 0)
                  {
                      printf(" (*** NO MOUNT POINTS ***)");
                  }
                  else
                  {
                      VARIANT* tmp = (VARIANT*)(mountPoints->pvData);
                      printf(" (");
                      for (ULONG j = 0; j < mountPoints->rgsabound[0].cElements; j++)
                      {
                          printf(" %ws ", tmp[j].bstrVal);
                      }
                      printf(")");
                  }
                  SafeArrayDestroyDataAndNull(mountPoints);
              }
              // Get the media type
              if (SUCCEEDED(hr))
              {
                  IDiscFormat2Data* dataWriter = NULL;
  
                  // create a DiscFormat2Data object
                  if (SUCCEEDED(hr))
                  {
                      hr = CoCreateInstance(CLSID_MsftDiscFormat2Data,
                                          NULL, CLSCTX_ALL,
                                          IID_PPV_ARGS(&dataWriter)
                                          );
                      if (FAILED(hr))
                      {
                          printf("Failed CoCreateInstance on dataWriter\n");
                          PrintHR(hr);
                      }
                  }
  
                  if (SUCCEEDED(hr))
                  {
                      hr = dataWriter->put_Recorder(discRecorder);
                      if (FAILED(hr))
                      {
                          printf("Failed dataWriter->put_Recorder\n");
                          PrintHR(hr);
                      }
                  }
                  // get the current media in the recorder
                  if (SUCCEEDED(hr))
                  {
                      IMAPI_MEDIA_PHYSICAL_TYPE mediaType = IMAPI_MEDIA_TYPE_UNKNOWN;
                      hr = dataWriter->get_CurrentPhysicalMediaType(&mediaType);
                      if (SUCCEEDED(hr))
                      {            
                          printf(" (%s)", g_MediaTypeStrings[mediaType]);
                      }
                  }
                  ReleaseAndNull(dataWriter);
              }
  
              printf("\n");
              FreeSysStringAndNull(venId);
              FreeSysStringAndNull(discId);
   
          }
          else
          {            
              printf("Failed to get drive %d\n", i);
          }
  
          ReleaseAndNull(discRecorder);
  
      }
  
      return hr;
  }
  
  
  HRESULT GetIsoStreamForDataWriting(__out IStream** result, __out ULONG* sectors2, WCHAR * shortStreamFilename)
  {
      HRESULT hr = S_OK;
      *result = NULL;
      *sectors2 = 0;
      
      IStream* data = NULL;
      ULONG tmpSectors = 0;
  
      {
          STATSTG stat; RtlZeroMemory(&stat, sizeof(STATSTG));
          // open an ISO image for the stream
          if (SUCCEEDED(hr))
          {
              hr = SHCreateStreamOnFileW(shortStreamFilename,
                                          STGM_READ | STGM_SHARE_DENY_WRITE,
                                          &data
                                          );
              if (FAILED(hr))
              {
                  printf("Failed to open file %S\n",
                              shortStreamFilename);
                  PrintHR(hr);
              }
          }
          // validate size and save # of blocks for use later
          if (SUCCEEDED(hr))
          {
              hr = data->Stat(&stat, STATFLAG_NONAME);
              if (FAILED(hr))
              {
                  printf("Failed to get stats for file\n");
                  PrintHR(hr);
              }
          }
          // validate size and save # of blocks for use later
          if (SUCCEEDED(hr))
          {
              if (stat.cbSize.QuadPart % 2048 != 0)
              {
                  printf("File is not multiple of 2048 bytes.  File size is %I64d (%I64x)\n",
                              stat.cbSize.QuadPart, stat.cbSize.QuadPart);
                  hr = E_FAIL;
              }
              else if (stat.cbSize.QuadPart / 2048 > 0x7FFFFFFF)
              {
                  printf("File is too large, # of sectors won't fit a LONG.  File size is %I64d (%I64x)\n",
                              stat.cbSize.QuadPart, stat.cbSize.QuadPart);
                  hr = E_FAIL;
              }
              else
              {
                  tmpSectors = (ULONG)(stat.cbSize.QuadPart / 2048);
              }
          }
      }
  
      if (SUCCEEDED(hr))
      {
          *result = data;
          *sectors2 = tmpSectors;
      }
      else
      {
          ReleaseAndNull(data);
      }
      return hr;
  }
  
  
  
  
  
  HRESULT ImageWriter(PROGRAM_OPTIONS options)
  {
      HRESULT hr = S_OK;
      IDiscRecorder2* recorder = NULL;
      IDiscFormat2Data* dataWriter = NULL;
      IStream * dataStream = NULL;
      ULONG sectorsInDataStream = 0;
      BOOLEAN dualLayerDvdMedia = FALSE;
      ULONG index = options.WriterIndex;
      CComObject<CTestDataWriter2Event>* eventSink = NULL;
  
      // create a DiscFormat2Data object
      if (SUCCEEDED(hr))
      {
          hr = CoCreateInstance(CLSID_MsftDiscFormat2Data,
                                NULL, CLSCTX_ALL,
                                IID_PPV_ARGS(&dataWriter)
                                );
          if (FAILED(hr))
          {
              printf("CoCreateInstance failed\n");
              PrintHR(hr);
          }
      }
      // create a DiscRecorder object
      if (SUCCEEDED(hr))
      {
          hr = GetDiscRecorder(index, &recorder);
          if (FAILED(hr))
          {
              printf("CoCreateInstance failed\n");
              PrintHR(hr);
          }
      }
  
      // Set the recorder as the recorder for the data writer
      if (SUCCEEDED(hr))
      {
          hr = dataWriter->put_Recorder(recorder);
          if (FAILED(hr))
          {
              printf("Failed put_Recorder()\n");
              PrintHR(hr);
          }
      }
      // release the recorder early...
      ReleaseAndNull(recorder);
  
      // Set the app name for use with exclusive access
      // THIS IS REQUIRED
      if (SUCCEEDED(hr))
      {
          BSTR appName = ::SysAllocString(L"Imapi2Sample");
  
          hr = dataWriter->put_ClientName(appName);
          if (FAILED(hr))
          {
              printf("FAILED to set client name for ISO write!\n");
              PrintHR(hr);
          }
          FreeSysStringAndNull(appName);
      }
     
      // verify the Current media write speed property gets
      //if (SUCCEEDED(hr))
      //{
      //    LONG value = 0;
      //    hr = dataWriter->get_CurrentMediaWriteSpeed(&value);
      //    if (FAILED(hr))
      //    {
      //        DoTrace((TRACE_LEVEL_INFORMATION, DebugGeneral,
      //                 "Failed get_CurrentMediaWriteSpeed() %!HRESULT!",
      //                 hr
      //                 ));
      //       printf("Ignoring failed get_CurrentWriteSpeed() for now\n");
      //        hr = S_OK;
      //    }
      //}
   
   
      // verify the SupportedMediaTypes property gets
      if (SUCCEEDED(hr))
      {
          SAFEARRAY * value = NULL;
          hr = dataWriter->get_SupportedMediaTypes(&value);
          if (FAILED(hr))
          {
              printf("Failed get_SupportedMediaTypes()\n");
              PrintHR(hr);
          }
          SafeArrayDestroyDataAndNull(value);
      }
    
      // get a stream to write to the disc
      if (SUCCEEDED(hr))
      {
          hr = GetIsoStreamForDataWriting(&dataStream, &sectorsInDataStream, options.FileName);
          if (FAILED(hr))
          {
              printf("Failed to create data stream for writing\n");
              PrintHR(hr);
          }
      }
  
      // Create the event sink
      if (SUCCEEDED(hr))
      {
          hr = CComObject<CTestDataWriter2Event>::CreateInstance(&eventSink);
          if (FAILED(hr))
          {
              printf("Failed to create event sink\n");
              PrintHR(hr);
          }
      }
  
      // need to keep a reference to this eventSink
      if (SUCCEEDED(hr))
      {
          hr = eventSink->AddRef();
          if (FAILED(hr))
          {
              printf("FAILED to addref\n");
              PrintHR(hr);
          }
      }
  
      // Hookup the event sink
      if (SUCCEEDED(hr))
      {
          hr = eventSink->DispEventAdvise(dataWriter);
          if (FAILED(hr))
          {
              printf("Failed to hookup event sink\n");
              PrintHR(hr);
          }
      }
  
      // write the stream
      if (SUCCEEDED(hr))
      {
          hr = dataWriter->Write(dataStream);
          if (FAILED(hr))
          {
              printf("Failed to write stream\n");
              PrintHR(hr);
          }
      }
  
      // unhook events
          // unhook events
      if (NULL != eventSink)
      {
          eventSink->DispEventUnadvise(dataWriter);
      }
  
      // verify that clearing the disc recorder works
      if (SUCCEEDED(hr))
      {
          hr = dataWriter->put_Recorder(NULL);
          if (FAILED(hr))
          {
              printf("Failed put_Recorder(NULL)\n");
              PrintHR(hr);
          }
      }
  
      ReleaseAndNull(eventSink);
      ReleaseAndNull(dataWriter);
      ReleaseAndNull(dataStream);
  
      if (SUCCEEDED(hr))
      {
          printf("ImageWriter succeeded for drive index %d\n",
                 index
                 );
      }
      else
      {
          printf("ImageWriter FAILED for drive index %d\n", index);
          PrintHR(hr);
      }
      return hr;
  }
  
  
  
  HRESULT EjectClose(PROGRAM_OPTIONS options, BOOLEAN close)
  {
      HRESULT hr = S_OK;
      IDiscRecorder2* recorder = NULL;
      ULONG index = options.WriterIndex;
      BOOL disableMCN = FALSE;
  
      // create a DiscRecorder object
      if (SUCCEEDED(hr))
      {
          hr = GetDiscRecorder(index, &recorder);
          if (FAILED(hr))
          {
              printf("FAILED to get disc recorder for eject/close\n");
              PrintHR(hr);
          }
      }
  
      // Try and prevent shell pop-ups
      if (SUCCEEDED(hr))
      {
          hr = recorder->DisableMcn();
          if (FAILED(hr))
          {
              printf("FAILED to enable MCN after Eject/Close\n");
              PrintHR(hr);
          }
          else
          {
              // Will use this flag instead of HR, in case there is a failure later on
              // we still want to enable MCN
              disableMCN = TRUE;
          }
      }
  
      if (SUCCEEDED(hr))
      {
          VARIANT_BOOL canLoad = VARIANT_FALSE;
          hr = recorder->get_DeviceCanLoadMedia(&canLoad);
          if (FAILED(hr))
          {
              printf("FAILED recorder->get_DeviceCanLoadMedia\n");
              PrintHR(hr);
          }
          if (canLoad && close)
          {
              hr = recorder->CloseTray();
              if (FAILED(hr))
              {
                  printf("FAILED recorder->CloseTray()\n");
                  PrintHR(hr);
              }
          }
          else if (canLoad)
          {
              hr = recorder->EjectMedia();
              if (FAILED(hr))
              {
                  printf("FAILED recorder->EjectMedia()\n");
                  PrintHR(hr);
              }
          }
      }
  
      // re-enable MCN
      if (disableMCN)
      {
          hr = recorder->EnableMcn();
          if (FAILED(hr))
          {
              printf("FAILED to enable MCN after Eject/Close\n");
              PrintHR(hr);
          }
      }
   
      ReleaseAndNull(recorder);
  
      if (SUCCEEDED(hr))
      {
  
      }
      else
      {    
          printf("EjectClose FAILED for drive index %d\n",
                 index
                 );
          PrintHR(hr);
      }
      return hr;
  }
  
  // Write wav files to CD
  HRESULT AudioWriter(PROGRAM_OPTIONS options)
  {
      HRESULT hr = S_OK;
      IDiscRecorder2* recorder = NULL;
      IDiscFormat2TrackAtOnce * audioWriter = NULL;
      CComObject<CAudioEvent>* eventSink = NULL;
  
      BOOLEAN bReturn = TRUE;
      ULONG index = options.WriterIndex;
  
      BSTR dir = ::SysAllocString( options.FileName );
  
      // create a IDiscFormat2TrackAtOnce object
      if (SUCCEEDED(hr))
      {
          hr = CoCreateInstance(CLSID_MsftDiscFormat2TrackAtOnce,
                                NULL, CLSCTX_ALL,
                                IID_PPV_ARGS(&audioWriter)
                                );
          if (FAILED(hr))
          {
              printf("Failed CoCreateInstance on dataWriter\n");
              PrintHR(hr);
          }
      }
  
      // create a DiscRecorder object
      if (SUCCEEDED(hr))
      {
          hr = GetDiscRecorder(index, &recorder);
          if (FAILED(hr))
          {
              printf("Failed GetDiscRecorder\n");
              PrintHR(hr);
          }
      }
  
      // Set the recorder as the recorder for the audio writer
      if (SUCCEEDED(hr))
      {
          hr = audioWriter->put_Recorder(recorder);
          if (FAILED(hr))
          {
              printf("Failed audioWriter->put_Recorder\n");
              PrintHR(hr);
          }
      }
  
      // Set the app name for use with exclusive access
      // THIS IS REQUIRED
      if (SUCCEEDED(hr))
      {
          BSTR appName = ::SysAllocString(L"Imapi2Sample");
  
          hr = audioWriter->put_ClientName(appName);
          if (FAILED(hr))
          {
              printf("FAILED to set client name for Audio!\n");
              PrintHR(hr);
          }
          FreeSysStringAndNull(appName);
      } 
  
      // get the current media in the recorder
      if (SUCCEEDED(hr))
      {
          IMAPI_MEDIA_PHYSICAL_TYPE mediaType = IMAPI_MEDIA_TYPE_UNKNOWN;
          hr = audioWriter->get_CurrentPhysicalMediaType(&mediaType);
          if (FAILED(hr))
          {
              printf("FAILED audioWriter->get_CurrentPhysicalMediaType\n");
              PrintHR(hr);
          }
      }
  
      if (SUCCEEDED(hr))
      {
          VARIANT_BOOL b;
          hr = audioWriter->IsCurrentMediaSupported(recorder, &b);
          if (FAILED(hr))
          {
              hr = S_OK;
          }
      }
  
      // NOW PREPARE THE MEDIA, it will be ready to use
      if (SUCCEEDED(hr))
      {
          hr = audioWriter->PrepareMedia();
          if (FAILED(hr))
          {
              printf("Failed audioWriter->PrepareMedia()\n");
              PrintHR(hr);
          }
      }
  
      //if (SUCCEEDED(hr) && !options.Close)
      //{
      //    hr = audioWriter->put_DoNotFinalizeDisc(VARIANT_FALSE);
      //    if (FAILED(hr))
      //    {
      //        printf("FAILED audioWriter->get_NumberOfExistingTracks\n");
      //        PrintHR(hr);
      //    }
      //}
  
      // hookup events
      // create an event object for the write engine
      if (SUCCEEDED(hr))
      {
          hr = CComObject<CAudioEvent>::CreateInstance(&eventSink);
          if (FAILED(hr))
          {
              printf("Failed to create event sink\n");
              PrintHR(hr);
          }
          else
          {
              eventSink->AddRef();
          }
      }
  
      // hookup the event
      if (SUCCEEDED(hr))
      {
          hr = eventSink->DispEventAdvise(audioWriter);
          if (FAILED(hr))
          {
              printf("Failed to hookup event sink\n");
              PrintHR(hr);
          }
      }
  
      // Add a track
      if (SUCCEEDED(hr))
      {
  	    WCHAR             AppendPath[MAX_PATH];
          WCHAR             FullPath[MAX_PATH];
  	    DWORD             ReturnCode;
  //	    DWORD             FileAttributes;
  	    HANDLE            Files;
  	    WIN32_FIND_DATAW  FileData;
  	    memset(&FileData, 0, sizeof(WIN32_FIND_DATA));
  
  
      	StringCchPrintfW(AppendPath, (sizeof(AppendPath))/(sizeof(AppendPath[0])), (L"%s\\*"), options.FileName);
  	    Files = FindFirstFileW(AppendPath, &FileData);
  
  		if (INVALID_HANDLE_VALUE != Files) {
  			//We have the search handle for the first file.
  			ReturnCode = 1;
  			while (ReturnCode) {
  				if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                  {
  						//This is a real directory that we should deal with.
  						printf("Skipping directory %ws\n", FileData.cFileName);
  				} 
                  else 
                  {
                      // We have a file, let's add it
                      IStream * testStream = NULL;
                      STATSTG stat;
                      
                      StringCchPrintfW(FullPath, (sizeof(FullPath))/(sizeof(FullPath[0])), 
                                                  (L"%s\\%s"), options.FileName, FileData.cFileName);
                      printf("Attempting to add %ws\n", FullPath);
  
                      // get a stream to write to the disc
                      hr = SHCreateStreamOnFileW(FullPath,
                                          STGM_READWRITE,
                                          &testStream
                                          );
  
                      if (FAILED(hr))
                      {
                          printf("FAILED to get file stream\n");
                          PrintHR(hr);
                      }
                      
                      if (SUCCEEDED(hr))
                      {
                          hr = testStream->Stat(&stat, STATFLAG_DEFAULT);
                          if (FAILED(hr))
                          {
                              printf("FAILED to get testStream->Stat\n");
                              PrintHR(hr);
                          }
                      }
  
                      // Mod the size so that it is 2352 byte aligned
                      if (SUCCEEDED(hr))
                      {
                          ULARGE_INTEGER newSize;
  
                          newSize.HighPart = stat.cbSize.HighPart;
                          newSize.LowPart = stat.cbSize.LowPart - (stat.cbSize.LowPart%2352);
  
                          hr = testStream->SetSize(newSize);
                          if (FAILED(hr))
                          {
                              printf("FAILED testStream->SetSize\n");
                              PrintHR(hr);
                          }
                      }
  
                      if (SUCCEEDED(hr))
                      {
                          //write the stream
                          printf("adding track now...\n");
                          hr = audioWriter->AddAudioTrack(testStream);
                          if (FAILED(hr))
                          {
                              printf("FAILED audioWriter->AddAudioTrack(testStream)\n");
                              PrintHR(hr);
                          }
                          else
                          {
                              printf("\n");
                          }
                      }
  
                      ReleaseAndNull(testStream);
  
  				}
  				memset(&FileData, 0, sizeof(WIN32_FIND_DATA));
  				ReturnCode = FindNextFileW(Files, &FileData);
  			}
  
  			if (!ReturnCode) 
              {
  				ReturnCode = GetLastError();
  				if (ReturnCode != ERROR_NO_MORE_FILES) 
                  {
  					printf("Error in attempting to get the next file in %s\n.",
  							AppendPath);
  				} 
                  else
                  {
  					printf("No More Files\n");
  				}
  			}
  			FindClose(Files);
  		}
          else
          {
              ReturnCode = GetLastError();
  			printf("Could not open a search handle on %ws.\n", options.FileName);
              printf("return = %d\n", ReturnCode);
  		}
      }
  
      // unhook events
      if (NULL != eventSink)
      {
          eventSink->DispEventUnadvise(audioWriter);
      }  
  
      // Release the media now that we are done
      if (SUCCEEDED(hr))
      {
          hr = audioWriter->ReleaseMedia();
          if (FAILED(hr))
          {
              printf("FAILED audioWriter->ReleaseMedia()\n");
              PrintHR(hr);
          }
      } 
  
      // Let's clear the recorder also
      if (SUCCEEDED(hr))
      {
          hr = audioWriter->put_Recorder(NULL);
          if (FAILED(hr))
          {
              printf("FAILED audioWriter->put_Recorder(NULL)\n");
              PrintHR(hr);
          }
      }
  
      ReleaseAndNull(eventSink);
      ReleaseAndNull(audioWriter);
      ReleaseAndNull(recorder);
  
      if (SUCCEEDED(hr))
      {
          printf("AudioWriter succeeded for drive index %d\n",
                 index
                 );
      }
      else
      {
          printf("AudioWriter FAILED for drive index %d\n", index);
          PrintHR(hr);
      }
      return hr;
  }
  
  // Write audio to disc using disc at once
  HRESULT RawWriter(PROGRAM_OPTIONS options)
  {
      HRESULT hr = S_OK;
  
      ::ATL::CComPtr<IRawCDImageCreator> raw;
      ::ATL::CComPtr<IStream> resultStream;
      ATL::CComPtr<IDiscRecorder2> iDiscRecorder;
      ATL::CComPtr<IDiscFormat2RawCD> iDiscFormatRaw;
      ATL::CComObject<CTestRawWriter2Event> *events = NULL;
      ULONG index = options.WriterIndex;
      
      // cocreate all burning classes
  
      // create a DiscRecorder object
      if (SUCCEEDED(hr))
      {
          hr = GetDiscRecorder(index, &iDiscRecorder);
          if (FAILED(hr))
          {
              printf("Failed GetDiscRecorder\n");
              PrintHR(hr);
          }
      }
  
      if (SUCCEEDED(hr))
      {
          hr = iDiscFormatRaw.CoCreateInstance(CLSID_MsftDiscFormat2RawCD);
  
          if (FAILED(hr))
          {
              printf("FAILED iDiscFormatRaw.CoCreateInstance\n");
              PrintHR(hr);
          }
      }
  
      // attach disc recorder and disc format
      if (SUCCEEDED(hr))
      {
          hr = iDiscFormatRaw->put_Recorder(iDiscRecorder);
  
          if (FAILED(hr))
          {
              printf("FAILED iDiscFormatRaw->put_Recorder\n");
              PrintHR(hr);
          }
      }
  
      // Set the app name for use with exclusive access
      // THIS IS REQUIRED
      if (SUCCEEDED(hr))
      {
          BSTR appName = ::SysAllocString(L"Imapi2Sample");
  
          hr = iDiscFormatRaw->put_ClientName(appName);
          if (FAILED(hr))
          {
              printf("FAILED to set client name for Raw!\n");
              PrintHR(hr);
          }
          FreeSysStringAndNull(appName);
      } 
  
      // check if the current recorder and media support burning
      VARIANT_BOOL recorderSupported = VARIANT_FALSE;
      VARIANT_BOOL mediaSupported = VARIANT_FALSE;
  
      if (SUCCEEDED(hr))
      {
          hr = iDiscFormatRaw->IsRecorderSupported(iDiscRecorder, &recorderSupported);
  
          if (FAILED(hr))
          {
              printf("FAILED iDiscFormatRaw->IsRecorderSupported!\n");
              PrintHR(hr);
          }
  
          if (recorderSupported != VARIANT_TRUE)
          {
              printf("ERROR: recorder reports it doesn't support burning DAO RAW capabilities!\n");
              hr = E_FAIL;
          }
      } 
  
      if (SUCCEEDED(hr))
      {
          hr = iDiscFormatRaw->IsCurrentMediaSupported(iDiscRecorder, &mediaSupported);
  
          if (FAILED(hr))
          {
              printf("FAILED iDiscFormatRaw->IsCurrentMediaSupported!\n");
              PrintHR(hr);
          }
  
          if (mediaSupported != VARIANT_TRUE)
          {
              printf("ERROR: recorder reports the current media doesn't support burning DAO RAW!\n");
              hr = E_FAIL;
          }
      }
      
      // create a raw image creator
      if (SUCCEEDED(hr))
      {
          hr = raw.CoCreateInstance(CLSID_MsftRawCDImageCreator);
  
          if (FAILED(hr))
          {
              printf("FAILED raw.CoCreateInstance\n");
              PrintHR(hr);
          }
      }
      
      // set the image type
      if (SUCCEEDED(hr))
      {
          hr = raw->put_ResultingImageType(IMAPI_FORMAT2_RAW_CD_SUBCODE_IS_RAW); //IMAPI_FORMAT2_RAW_CD_SUBCODE_PQ_ONLY);
  
          if (FAILED(hr))
          {
              printf("FAILED raw->put_ResultingImageType\n");
              PrintHR(hr);
          }
      }
  
      // Add tracks
      if (SUCCEEDED(hr))
      {
          WCHAR             AppendPath[MAX_PATH];
          WCHAR             FullPath[MAX_PATH];
          DWORD             ReturnCode;
          HANDLE            Files;
          WIN32_FIND_DATAW  FileData;
          memset(&FileData, 0, sizeof(WIN32_FIND_DATA));
          LONG index = 0; 
  
  
      	StringCchPrintfW(AppendPath, (sizeof(AppendPath))/(sizeof(AppendPath[0])), (L"%s\\*"), options.FileName);
  	    Files = FindFirstFileW(AppendPath, &FileData);
  
  		if (INVALID_HANDLE_VALUE != Files) {
  			//We have the search handle for the first file.
  			ReturnCode = 1;
  			while (ReturnCode) {
  				if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                  {
  						//This is a real directory that we should deal with.
  						printf("Skipping directory %ws\n", FileData.cFileName);
  				} 
                  else 
                  {
                      // We have a file, let's add it
                      IStream * testStream = NULL;
                      STATSTG stat;
                      
                      StringCchPrintfW(FullPath, (sizeof(FullPath))/(sizeof(FullPath[0])), 
                                                  (L"%s\\%s"), options.FileName, FileData.cFileName);
                      printf("Attempting to add %ws\n", FullPath);
  
                      // get a stream to write to the disc
                      hr = SHCreateStreamOnFileW(FullPath,
                                          STGM_READWRITE,
                                          &testStream
                                          );
  
                      if (FAILED(hr))
                      {
                          printf("FAILED to get file stream\n");
                          PrintHR(hr);
                      }
                      
                      if (SUCCEEDED(hr))
                      {
                          hr = testStream->Stat(&stat, STATFLAG_DEFAULT);
                          if (FAILED(hr))
                          {
                              printf("FAILED to get testStream->Stat\n");
                              PrintHR(hr);
                          }
                      }
  
                      // Mod the size so that it is 2352 byte aligned
                      if (SUCCEEDED(hr))
                      {
                          ULARGE_INTEGER newSize;
  
                          newSize.HighPart = stat.cbSize.HighPart;
                          newSize.LowPart = stat.cbSize.LowPart - (stat.cbSize.LowPart%2352);
  
                          hr = testStream->SetSize(newSize);
                          if (FAILED(hr))
                          {
                              printf("FAILED testStream->SetSize\n");
                              PrintHR(hr);
                          }
                      }
  
                      if (SUCCEEDED(hr))
                      {
                          //Add the track to the stream
                          printf("adding track now...\n");
                          hr = raw->AddTrack(IMAPI_CD_SECTOR_AUDIO, testStream, &index);
                          if (FAILED(hr))
                          {
                              printf("FAILED raw->AddTrack(testStream)\n");
                              PrintHR(hr);
                          }
                          else
                          {
                              printf("\n");
                          }
                      }
  
                      ReleaseAndNull(testStream);
  
  				}
  				memset(&FileData, 0, sizeof(WIN32_FIND_DATA));
  				ReturnCode = FindNextFileW(Files, &FileData);
  			}
  
  			if (!ReturnCode) 
              {
  				ReturnCode = GetLastError();
  				if (ReturnCode != ERROR_NO_MORE_FILES) 
                  {
  					printf("Error in attempting to get the next file in %s\n.",
  							AppendPath);
  				} 
                  else
                  {
  					printf("No More Files\n");
  				}
  			}
  			FindClose(Files);
  		}
          else
          {
              ReturnCode = GetLastError();
  			printf("Could not open a search handle on %ws.\n", options.FileName);
              printf("return = %d\n", ReturnCode);
  		}
      }
  
      // create the disc image    
      if (SUCCEEDED(hr))
      {
          raw->CreateResultImage(&resultStream);
  
          if (FAILED(hr))
          {
              printf("FAILED raw->CreateResultImage\n");
              PrintHR(hr);
          }
      }
  
      // prepare media
      if (SUCCEEDED(hr))
      {
          hr = iDiscFormatRaw->PrepareMedia();
  
          if (FAILED(hr))
          {
              printf("FAILED iDiscFormatRaw->PrepareMedia\n");
              PrintHR(hr);
          }
      }
  
      // set up options on disc format
      // NOTE: this will change later to put a different mode when it's fully implemented
      if (SUCCEEDED(hr))
      {
          hr = iDiscFormatRaw->put_RequestedSectorType(IMAPI_FORMAT2_RAW_CD_SUBCODE_IS_RAW); //IMAPI_FORMAT2_RAW_CD_SUBCODE_PQ_ONLY);
  
          if (FAILED(hr))
          {
              printf("FAILED iDiscFormatRaw->put_RequestedSectorType\n");
              PrintHR(hr);
          }
      }
  
      // connect events
      //if (SUCCEEDED(hr))
      //{
      //    hr = events->DispEventAdvise(iDiscFormatRaw);
  
      //    if (FAILED(hr))
      //    {
      //        printf("FAILED events->DispEventAdvise\n");
      //        PrintHR(hr);
      //    }
      //}
  
      // burn stream
      if (SUCCEEDED(hr))
      {
          hr = iDiscFormatRaw->WriteMedia(resultStream);
  
          if (FAILED(hr))
          {
              printf("FAILED iDiscFormatRaw->WriteMedia\n");
              PrintHR(hr);
          }
      }
  
      // unadvise events
      //if (events != NULL)
      //{
      //    events->DispEventUnadvise(iDiscFormatRaw);
      //}
  
      // release media (even if the burn failed)
      hr = iDiscFormatRaw->ReleaseMedia();
  
      return hr;
  }
  
  // Function for writing a dir to disc
  HRESULT DataWriter(PROGRAM_OPTIONS options)
  {
      HRESULT           hr = S_OK;
      IDiscRecorder2*   discRecorder = NULL;
      IDiscFormat2Data* dataWriter = NULL;
      IStream*          dataStream = NULL;
      BOOLEAN           dualLayerDvdMedia = FALSE;
      ULONG             index = options.WriterIndex;
      VARIANT_BOOL      isBlank = FALSE;
      
      SYSTEMTIME startTime;
      SYSTEMTIME endTime;
      SYSTEMTIME elapsedTime;
  
      IFileSystemImage *       image = NULL;
      IFileSystemImageResult * fsiresult = NULL;
      IFsiDirectoryItem *      root = NULL;
      BSTR                     dir = ::SysAllocString( options.FileName );
      IBootOptions *           pBootOptions = NULL;
      IStream *                bootStream = NULL;
  
      CComObject<CTestDataWriter2Event>* eventSink = NULL;
  
      // create a DiscFormat2Data object
      if (SUCCEEDED(hr))
      {
          hr = CoCreateInstance(CLSID_MsftDiscFormat2Data,
                                NULL, CLSCTX_ALL,
                                IID_PPV_ARGS(&dataWriter)
                                );
          if (FAILED(hr))
          {
              printf("FAILED CoCreateInstance\n");
              PrintHR(hr);
          }
      }
      // create a DiscRecorder object
      if (SUCCEEDED(hr))
      {
          hr = GetDiscRecorder(index, &discRecorder);
          if (FAILED(hr))
          {
              printf("FAILED GetDiscRecorder\n");
              PrintHR(hr);
          }
      }
  
      // Set the recorder as the recorder for the data writer
      if (SUCCEEDED(hr))
      {
          hr = dataWriter->put_Recorder(discRecorder);
          if (FAILED(hr))
          {
              printf("FAILED to put_Recorder\n");
              PrintHR(hr);
          }
      }
  
      // Set the app name for use with exclusive access
      // THIS IS REQUIRED
      if (SUCCEEDED(hr))
      {
          BSTR appName = ::SysAllocString(L"Imapi2Sample");
  
          hr = dataWriter->put_ClientName(appName);
          if (FAILED(hr))
          {
              printf("FAILED to set client name for erase!\n");
              PrintHR(hr);
          }
          FreeSysStringAndNull(appName);
      }
  
      // verify the SupportedMediaTypes property gets
      if (SUCCEEDED(hr))
      {
          SAFEARRAY * value = NULL;
          hr = dataWriter->get_SupportedMediaTypes(&value);
          if (FAILED(hr))
          {
              printf("FAILED to get_SupportedMediaTypes\n");
              PrintHR(hr);
          }
          SafeArrayDestroyDataAndNull(value);
      }
      
      // Close the disc if specified
      if (SUCCEEDED(hr) && options.CloseDisc)
      {
          printf("Disc will be closed\n");
          hr = dataWriter->put_ForceMediaToBeClosed(VARIANT_TRUE);
          if (FAILED(hr))
          {
              printf("FAILED to put_ForceMediaToBeClosed\n");
              PrintHR(hr);
          }
      }
  
      // verify the StartAddressOfPreviousSession property 
      // ALSO -- for DVD+R DL, if from sector zero, set to finalize media
      //if (SUCCEEDED(hr))
      //{
      //    LONG value = 0;
      //    hr = dataWriter->get_StartAddressOfPreviousSession(&value);
      //    if (FAILED(hr))
      //    {
      //        printf("FAILED to get_StartAddressOfPreviousSession\n");
      //        PrintHR(hr);
      //    }
      //    else if (value == ((ULONG)-1))
      //    {
      //        hr = dataWriter->put_ForceMediaToBeClosed(VARIANT_TRUE);
      //        if (FAILED(hr))
      //        {
      //            printf("FAILED to put_ForceMediaToBeClosed\n");
      //            PrintHR(hr);
      //        }
      //    }
      //}   
      //   
      // get a stream to write to the disc
      // create a ID_IFileSystemImage object
      if (SUCCEEDED(hr))
      {
          hr = CoCreateInstance(CLSID_MsftFileSystemImage,
                                NULL, CLSCTX_ALL,
                                IID_PPV_ARGS(&image)
                                );
          if (FAILED(hr))
          {
              printf("Failed CoCreate for filesystem\n");
              PrintHR(hr);
          }
      }
  
      // Set the filesystems to use if specified
      if (SUCCEEDED(hr) && (options.Iso || options.Joliet || options.UDF))
      {
          FsiFileSystems fileSystem = FsiFileSystemNone;
          if (options.Iso)
          {
              fileSystem = (FsiFileSystems)(fileSystem | FsiFileSystemISO9660);
          }
          if (options.Joliet)
          {
              fileSystem = (FsiFileSystems)(fileSystem | FsiFileSystemJoliet);
              fileSystem = (FsiFileSystems)(fileSystem | FsiFileSystemISO9660);
          }
          if (options.UDF)
          {
              fileSystem = (FsiFileSystems)(fileSystem | FsiFileSystemUDF);
          }
  
          hr = image->put_FileSystemsToCreate(fileSystem);
          if (FAILED(hr))
          {
              printf("Failed to put PileSystemsToCreate\n");
              PrintHR(hr);
          }
      }
  
      // Get the root dir
      if (SUCCEEDED(hr))
      {       
          hr = image->get_Root(&root);
          if (FAILED(hr))
          {
              printf("Failed to get root directory\n");
              PrintHR(hr);
          }
      }
      // create the BootImageOptions object
      if (SUCCEEDED(hr) && (NULL != options.BootFileName))
      {
          hr = CoCreateInstance(CLSID_BootOptions,
                                NULL, CLSCTX_ALL,
                                IID_PPV_ARGS(&pBootOptions)
                                );
          if (FAILED(hr))
          {
              printf("FAILED cocreate bootoptions\n");
              PrintHR(hr);
          }
  
          if (SUCCEEDED(hr))
          {
              hr = SHCreateStreamOnFileW(options.BootFileName,
                      STGM_READ | STGM_SHARE_DENY_WRITE,
                      &bootStream
                      );
  
              if (FAILED(hr))
              {
                  printf("Failed SHCreateStreamOnFileW for BootImage\n");
                  PrintHR(hr);
              }
          }
  
          if (SUCCEEDED(hr))
          {
              hr = pBootOptions->AssignBootImage(bootStream);
              if (FAILED(hr))
              {
                  printf("Failed BootImage put_BootImage\n");
                  PrintHR(hr);
              }
          }            
  
          if (SUCCEEDED(hr))
          {
              hr = image->put_BootImageOptions(pBootOptions);
              if (FAILED(hr))
              {
                  printf("Failed BootImage put_BootImageOptions\n");
                  PrintHR(hr);
              }
          }                        
      }
  
      // Check if media is blank
      if (SUCCEEDED(hr))
      {
          hr = dataWriter->get_MediaHeuristicallyBlank(&isBlank);
          if (FAILED(hr))
          {
              printf("Failed to get_MediaHeuristicallyBlank\n");
              PrintHR(hr);
          }
      }
  
      if (SUCCEEDED(hr) && !options.Multi && !isBlank)
      {
          printf("*** WRITING TO NON-BLANK MEDIA WITHOUT IMPORT! ***\n");
      }
  
          // ImportFileSystem - Import file data from disc
      if (SUCCEEDED(hr) && options.Multi)
      {       
          FsiFileSystems fileSystems;
          SAFEARRAY* multiSession = NULL;
  
          // Get mutlisession interface to set in image
          if (SUCCEEDED(hr))
          {        
              hr = dataWriter->get_MultisessionInterfaces(&multiSession);
  
              if (FAILED(hr))
              {
                  printf("Failed dataWriter->MultisessionInterfaces\n");
                  PrintHR(hr);
              }
          }
  
          // Set the multisession interface in the image
          if (SUCCEEDED(hr))
          {
              hr = image->put_MultisessionInterfaces(multiSession);
  
              if (FAILED(hr))
              {
                  printf("Failed image->put_MultisessionInterfaces\n");
                  PrintHR(hr);
                  if (multiSession != NULL)
                  {
                      SafeArrayDestroy(multiSession);
                  }
              }
              multiSession = NULL;
          }
  
          if (SUCCEEDED(hr))
          {
              hr = image->ImportFileSystem(&fileSystems);
              if (FAILED(hr))
              {
                  if (hr == IMAPI_E_EMPTY_DISC)
                  {
                      printf("Empty Disc\n");
                      hr = S_OK;
                  }
                  else
                  {
                      printf("Failed to ImportFileSystem\n");
                      PrintHR(hr);
                  }
              }
          }
      }
  
      // Get free media blocks
      if (SUCCEEDED(hr))
      {
          LONG freeBlocks;
  
          hr = dataWriter->get_FreeSectorsOnMedia(&freeBlocks);        
          if (FAILED(hr))
          {
              printf("Failed to get Free Media Blocks\n");
              PrintHR(hr);
          }
          else
          {
              hr = image->put_FreeMediaBlocks(freeBlocks);
              if (FAILED(hr))
              {
                  printf("Failed to put Free Media Blocks\n");
                  PrintHR(hr);
              }
          }
      }
          
      // Add a dir to the image
      if (SUCCEEDED(hr))
      {
          printf("Adding %ws", dir);
          GetSystemTime(&startTime);       // gets current time
          hr = root->AddTree(dir, false);
          GetSystemTime(&endTime);       // gets current time
          if (FAILED(hr))
          {
              printf("\nFailed to add %ws to root dir using AddTree\n", dir);
              PrintHR(hr);
          }
          else
          {
              CalcElapsedTime(&startTime, &endTime, &elapsedTime);
              printf(" - Time: %02d:%02d:%02d\n", elapsedTime.wHour,
                                            elapsedTime.wMinute,
                                            elapsedTime.wSecond);
          }
      }
  
      // Free our bstr
      FreeSysStringAndNull( dir );
  
      // Check what file systems are being used
      if (SUCCEEDED(hr))
      {
          FsiFileSystems fileSystems;
          hr = image->get_FileSystemsToCreate(&fileSystems);
          if (FAILED(hr))
          {
              printf("Failed image->get_FileSystemsToCreate\n");
              PrintHR(hr);
          }
          else
          {
              printf("Supported file systems: ");
              if (fileSystems & FsiFileSystemISO9660)
              {
                  printf("ISO9660 ");
              }
              if (fileSystems & FsiFileSystemJoliet)
              {
                  printf("Joliet ");
              }
              if (fileSystems & FsiFileSystemUDF)
              {
                  printf("UDF ");
              }
              printf("\n");
          }
      }
  
      // Get count
      if (SUCCEEDED(hr))
      {
          LONG numFiles = 0;
          LONG numDirs = 0;
          if (SUCCEEDED(hr)) { hr = image->get_FileCount(&numFiles); }
          if (SUCCEEDED(hr)) { hr = image->get_DirectoryCount(&numDirs); }
          if (FAILED(hr))
          {
              printf("Failed image->get_FileCount\n");
              PrintHR(hr);
          }
          else
          {
              printf("Number of Files: %d\n", numFiles);
              printf("Number of Directories: %d\n", numDirs);
          }
      }
      
      //Set the volume name
      if (SUCCEEDED(hr) && (NULL != options.VolumeName))
      {
          BSTR volName = options.VolumeName;
          hr = image->put_VolumeName(volName);
          if (FAILED(hr))
          {
              printf("Failed setting Volume Name\n");
              PrintHR(hr);
          }
  
          FreeSysStringAndNull(volName);
      }
  
      // Create the result image
      if (SUCCEEDED(hr))
      {
          hr = image->CreateResultImage(&fsiresult);
          if (FAILED(hr))
          {
              printf("Failed to get result image, returned %08x\n", hr);
          }
      }
  
      // Get the stream
      if (SUCCEEDED(hr))
      {
          hr = fsiresult->get_ImageStream(&dataStream);
          if (FAILED(hr))
          {
              printf("Failed to get stream, returned %08x\n", hr);
          }
          else
          {
              printf("Image ready to write\n");
          }
      }
  
      // Create event sink
      if (SUCCEEDED(hr))
      {
          hr = CComObject<CTestDataWriter2Event>::CreateInstance(&eventSink);
          if (FAILED(hr))
          {
              printf("Failed to create event sink\n");
              PrintHR(hr);
          }
          else
          {
              eventSink->AddRef();
          }
      }
  
      // hookup event sink
      if (SUCCEEDED(hr))
      {
          hr = eventSink->DispEventAdvise(dataWriter);
          if (FAILED(hr))
          {
              printf("Failed to hookup event sink\n");
              PrintHR(hr);
          }
      }
  
      // write the stream
      if (SUCCEEDED(hr))
      {        
          GetSystemTime(&startTime);
          hr = dataWriter->Write(dataStream);
          GetSystemTime(&endTime);
          if (FAILED(hr))
          {
              printf("Failed to write stream\n");
              PrintHR(hr);
          }
                  else
          {
              CalcElapsedTime(&startTime, &endTime, &elapsedTime);
              printf(" - Time to write: %02d:%02d:%02d\n", elapsedTime.wHour,
                                            elapsedTime.wMinute,
                                            elapsedTime.wSecond);
          }
      }
      // unhook events
      if (NULL != eventSink)
      {
          eventSink->DispEventUnadvise(dataWriter);
      }
      
      // verify the WriteProtectStatus property gets
      if (SUCCEEDED(hr))
      {
          IMAPI_MEDIA_WRITE_PROTECT_STATE value = (IMAPI_MEDIA_WRITE_PROTECT_STATE)0;
          hr = dataWriter->get_WriteProtectStatus(&value);
          if (FAILED(hr))
          {
              printf("Failed get_WriteProtectStatus()\n");
              PrintHR(hr);
          }
      }
      
      // verify that clearing the disc recorder works
      if (SUCCEEDED(hr))
      {
          hr = dataWriter->put_Recorder(NULL);
          if (FAILED(hr))
          {
              printf("Failed put_Recorder(NULL)\n");
              PrintHR(hr);
          }
  
      }
            
      ReleaseAndNull(eventSink);
      ReleaseAndNull(image);
      ReleaseAndNull(fsiresult);
      ReleaseAndNull(dataWriter);
      ReleaseAndNull(dataStream);
      ReleaseAndNull(discRecorder);
      ReleaseAndNull(pBootOptions);
      ReleaseAndNull(bootStream);
  
      if (SUCCEEDED(hr))
      {
          printf("DataWriter succeeded for drive index %d\n",
                 index
                 );
      }
      else
      {    
          printf("DataWriter FAILED for drive index %d\n",
                 index
                 );
          PrintHR(hr);
      }
      return hr;
  }
  
  class CConsoleModule :
      public ::ATL::CAtlExeModuleT<CConsoleModule>
  {
  };
  CConsoleModule _AtlModule;
  
  int __cdecl wmain(int argc, WCHAR *argv[])
  {
      HRESULT coInitHr = S_OK;
      HRESULT hr = S_OK;
      PROGRAM_OPTIONS options;
  
      SYSTEMTIME startTime;
      SYSTEMTIME endTime;
      SYSTEMTIME elapsedTime;
  
      if ( !ParseCommandLine( argc-1, argv+1, &options ) )
      {
          PrintHelp(argv[0]);
          hr = E_INVALIDARG;
          return hr;
      }
      else
      {
          //PrintOptions(&options);
      }
      
      // Get start time for total time
      GetSystemTime(&startTime);
  
       if (CAtlBaseModule::m_bInitFailed)
       {
           printf("AtlBaseInit failed...\n");
           coInitHr = E_FAIL;
       }
       else
       {
           // printf("AtlBaseInit passed...\n");
           coInitHr = S_OK;
       }
  
      if ( SUCCEEDED(coInitHr) && options.ListWriters )
      {
          hr = ListAllRecorders();
      }
      if ( SUCCEEDED(coInitHr) && options.Erase )
      {
          hr = EraseMedia( options.WriterIndex, options.FullErase );
      }
      if ( SUCCEEDED(coInitHr) && options.Write )
      {
          hr = DataWriter(options);
      }
      if ( SUCCEEDED(coInitHr) && options.Image )
      {
          hr = ImageWriter(options);
      }
      if ( SUCCEEDED(coInitHr) && options.Audio )
      {
          hr = AudioWriter(options);
      }
      if ( SUCCEEDED(coInitHr) && options.Raw )
      {
          hr = RawWriter(options);
      }
      if ( SUCCEEDED(coInitHr) && options.Eject )
      {
          hr = EjectClose(options, FALSE);
      }
      if ( SUCCEEDED(coInitHr) && options.Close )
      {
          hr = EjectClose(options, TRUE);
      }
  
      if (SUCCEEDED(coInitHr))
      {
          CoUninitialize();
      }
  
      GetSystemTime(&endTime);
  
      CalcElapsedTime(&startTime, &endTime, &elapsedTime);
      printf(" - Total Time: %02d:%02d:%02d\n", elapsedTime.wHour,
                                      elapsedTime.wMinute,
                                      elapsedTime.wSecond);
  
      if (SUCCEEDED(hr))
          return 0;
      else
      {
          PrintHR(hr);
          return 1;
      }
  }
  
  