Start the CD Burner Wizard

Introduction

The requirement: Start the CD Burner Wizard programatically, copying files to the staging area first.

Notes

XP & Vista come with a Wizard to help burn things to CD. The wizard assumes that you've already copied the files in question to the CD Burning area (something like: C:\Documents and Settings\Username\Local Settings\Application Data\Microsoft\CD Burning). The wizard is controllable via a small COM interface which does not inherit from IDispatch, so is not available via the win32com.client mechanism from pywin32, nor its comtypes.client equivalent. Instead, a specific COM object must be instantiated via its CLSID, and the ICDBurn interface specified.

The code below uses ctypes & comtypes throughout and for purposes of readability makes use of the from ... import * idiom which is generally frowned upon. The directory to be burnt to CD is passed into the main function as a parameter, and it is assumed that the CD Burning area is currently empty.

Thanks for Andreas Bunkahle for the question which led to this example

The Code

import os, sys
import shutil

from comtypes import *
import comtypes.client
from ctypes.wintypes import *

S_OK = 0
CSIDL_CDBURN_AREA = 59

##
## ICDBurn
##
IID_ICDBurn = "{3d73a659-e5d0-4d42-afc0-5121ba425c8d}"

class ICDBurn (IUnknown):
  _iid_ = GUID (IID_ICDBurn)
  _methods_ = [
    STDMETHOD (HRESULT, "GetRecorderDriveLetter", (LPWSTR, UINT)),
    STDMETHOD (HRESULT, "Burn", (HWND,)),
    STDMETHOD (HRESULT, "HasRecordableDrive", (POINTER (BOOL),))
  ]
  
def main (directory):
  CDBurn_GUID = '{fbeb8a05-beee-4442-804e-409d6c4515e9}'
  CDBurn = comtypes.client.CreateObject (CDBurn_GUID, interface=ICDBurn)

  has_recordable_drive = BOOL ()
  CDBurn.HasRecordableDrive (byref (has_recordable_drive))
  if not has_recordable_drive:
    raise RuntimeError ("No recordable drive found")

  letter = create_unicode_buffer (4)
  result = CDBurn.GetRecorderDriveLetter (letter, 4)
  if result != S_OK:
    raise RuntimeError ("Unable to get recorder drive letter: %d" % result)
  else:
    print "Using drive", letter.value

  path_buffer = create_unicode_buffer (1024)
  result = windll.shell32.SHGetFolderPathW (HWND (0), CSIDL_CDBURN_AREA, None, 0, path_buffer)
  if result != S_OK:
    raise RuntimeError ("Unable to find staging area: %d" % result)

  burn_area = path_buffer.value
  shutil.copytree (directory, os.path.join (burn_area, os.path.basename (directory)))

  CDBurn.Burn (0)

if __name__ == '__main__':
  main (*sys.argv[1:])