Home | History | Annotate | Download | only in msi
      1 # Microsoft Installer Library

      2 # (C) 2003 Martin v. Loewis

      3 
      4 import win32com.client.gencache
      5 import win32com.client
      6 import pythoncom, pywintypes
      7 from win32com.client import constants
      8 import re, string, os, sets, glob, subprocess, sys, _winreg, struct
      9 
     10 try:
     11     basestring
     12 except NameError:
     13     basestring = (str, unicode)
     14 
     15 # Partially taken from Wine

     16 datasizemask=      0x00ff
     17 type_valid=        0x0100
     18 type_localizable=  0x0200
     19 
     20 typemask=          0x0c00
     21 type_long=         0x0000
     22 type_short=        0x0400
     23 type_string=       0x0c00
     24 type_binary=       0x0800
     25 
     26 type_nullable=     0x1000
     27 type_key=          0x2000
     28 # XXX temporary, localizable?

     29 knownbits = datasizemask | type_valid | type_localizable | \
     30             typemask | type_nullable | type_key
     31 
     32 # Summary Info Property IDs

     33 PID_CODEPAGE=1
     34 PID_TITLE=2
     35 PID_SUBJECT=3
     36 PID_AUTHOR=4
     37 PID_KEYWORDS=5
     38 PID_COMMENTS=6
     39 PID_TEMPLATE=7
     40 PID_LASTAUTHOR=8
     41 PID_REVNUMBER=9
     42 PID_LASTPRINTED=11
     43 PID_CREATE_DTM=12
     44 PID_LASTSAVE_DTM=13
     45 PID_PAGECOUNT=14
     46 PID_WORDCOUNT=15
     47 PID_CHARCOUNT=16
     48 PID_APPNAME=18
     49 PID_SECURITY=19
     50 
     51 def reset():
     52     global _directories
     53     _directories = sets.Set()
     54 
     55 def EnsureMSI():
     56     win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0)
     57 
     58 def EnsureMSM():
     59     try:
     60         win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 1, 0)
     61     except pywintypes.com_error:
     62         win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 2, 0)
     63 
     64 _Installer=None
     65 def MakeInstaller():
     66     global _Installer
     67     if _Installer is None:
     68         EnsureMSI()
     69         _Installer = win32com.client.Dispatch('WindowsInstaller.Installer',
     70                      resultCLSID='{000C1090-0000-0000-C000-000000000046}')
     71     return _Installer
     72 
     73 _Merge=None
     74 def MakeMerge2():
     75     global _Merge
     76     if _Merge is None:
     77         EnsureMSM()
     78         _Merge = win32com.client.Dispatch("Msm.Merge2.1")
     79     return _Merge
     80 
     81 class Table:
     82     def __init__(self, name):
     83         self.name = name
     84         self.fields = []
     85 
     86     def add_field(self, index, name, type):
     87         self.fields.append((index,name,type))
     88 
     89     def sql(self):
     90         fields = []
     91         keys = []
     92         self.fields.sort()
     93         fields = [None]*len(self.fields)
     94         for index, name, type in self.fields:
     95             index -= 1
     96             unk = type & ~knownbits
     97             if unk:
     98                 print "%s.%s unknown bits %x" % (self.name, name, unk)
     99             size = type & datasizemask
    100             dtype = type & typemask
    101             if dtype == type_string:
    102                 if size:
    103                     tname="CHAR(%d)" % size
    104                 else:
    105                     tname="CHAR"
    106             elif dtype == type_short:
    107                 assert size==2
    108                 tname = "SHORT"
    109             elif dtype == type_long:
    110                 assert size==4
    111                 tname="LONG"
    112             elif dtype == type_binary:
    113                 assert size==0
    114                 tname="OBJECT"
    115             else:
    116                 tname="unknown"
    117                 print "%s.%sunknown integer type %d" % (self.name, name, size)
    118             if type & type_nullable:
    119                 flags = ""
    120             else:
    121                 flags = " NOT NULL"
    122             if type & type_localizable:
    123                 flags += " LOCALIZABLE"
    124             fields[index] = "`%s` %s%s" % (name, tname, flags)
    125             if type & type_key:
    126                 keys.append("`%s`" % name)
    127         fields = ", ".join(fields)
    128         keys = ", ".join(keys)
    129         return "CREATE TABLE %s (%s PRIMARY KEY %s)" % (self.name, fields, keys)
    130 
    131     def create(self, db):
    132         v = db.OpenView(self.sql())
    133         v.Execute(None)
    134         v.Close()
    135 
    136 class Binary:
    137     def __init__(self, fname):
    138         self.name = fname
    139     def __repr__(self):
    140         return 'msilib.Binary(os.path.join(dirname,"%s"))' % self.name
    141 
    142 def gen_schema(destpath, schemapath):
    143     d = MakeInstaller()
    144     schema = d.OpenDatabase(schemapath,
    145             win32com.client.constants.msiOpenDatabaseModeReadOnly)
    146 
    147     # XXX ORBER BY

    148     v=schema.OpenView("SELECT * FROM _Columns")
    149     curtable=None
    150     tables = []
    151     v.Execute(None)
    152     f = open(destpath, "wt")
    153     f.write("from msilib import Table\n")
    154     while 1:
    155         r=v.Fetch()
    156         if not r:break
    157         name=r.StringData(1)
    158         if curtable != name:
    159             f.write("\n%s = Table('%s')\n" % (name,name))
    160             curtable = name
    161             tables.append(name)
    162         f.write("%s.add_field(%d,'%s',%d)\n" %
    163                 (name, r.IntegerData(2), r.StringData(3), r.IntegerData(4)))
    164     v.Close()
    165 
    166     f.write("\ntables=[%s]\n\n" % (", ".join(tables)))
    167 
    168     # Fill the _Validation table

    169     f.write("_Validation_records = [\n")
    170     v = schema.OpenView("SELECT * FROM _Validation")
    171     v.Execute(None)
    172     while 1:
    173         r = v.Fetch()
    174         if not r:break
    175         # Table, Column, Nullable

    176         f.write("(%s,%s,%s," %
    177                 (`r.StringData(1)`, `r.StringData(2)`, `r.StringData(3)`))
    178         def put_int(i):
    179             if r.IsNull(i):f.write("None, ")
    180             else:f.write("%d," % r.IntegerData(i))
    181         def put_str(i):
    182             if r.IsNull(i):f.write("None, ")
    183             else:f.write("%s," % `r.StringData(i)`)
    184         put_int(4) # MinValue

    185         put_int(5) # MaxValue

    186         put_str(6) # KeyTable

    187         put_int(7) # KeyColumn

    188         put_str(8) # Category

    189         put_str(9) # Set

    190         put_str(10)# Description

    191         f.write("),\n")
    192     f.write("]\n\n")
    193 
    194     f.close()
    195 
    196 def gen_sequence(destpath, msipath):
    197     dir = os.path.dirname(destpath)
    198     d = MakeInstaller()
    199     seqmsi = d.OpenDatabase(msipath,
    200             win32com.client.constants.msiOpenDatabaseModeReadOnly)
    201 
    202     v = seqmsi.OpenView("SELECT * FROM _Tables");
    203     v.Execute(None)
    204     f = open(destpath, "w")
    205     print >>f, "import msilib,os;dirname=os.path.dirname(__file__)"
    206     tables = []
    207     while 1:
    208         r = v.Fetch()
    209         if not r:break
    210         table = r.StringData(1)
    211         tables.append(table)
    212         f.write("%s = [\n" % table)
    213         v1 = seqmsi.OpenView("SELECT * FROM `%s`" % table)
    214         v1.Execute(None)
    215         info = v1.ColumnInfo(constants.msiColumnInfoTypes)
    216         while 1:
    217             r = v1.Fetch()
    218             if not r:break
    219             rec = []
    220             for i in range(1,r.FieldCount+1):
    221                 if r.IsNull(i):
    222                     rec.append(None)
    223                 elif info.StringData(i)[0] in "iI":
    224                     rec.append(r.IntegerData(i))
    225                 elif info.StringData(i)[0] in "slSL":
    226                     rec.append(r.StringData(i))
    227                 elif info.StringData(i)[0]=="v":
    228                     size = r.DataSize(i)
    229                     bytes = r.ReadStream(i, size, constants.msiReadStreamBytes)
    230                     bytes = bytes.encode("latin-1") # binary data represented "as-is"

    231                     if table == "Binary":
    232                         fname = rec[0]+".bin"
    233                         open(os.path.join(dir,fname),"wb").write(bytes)
    234                         rec.append(Binary(fname))
    235                     else:
    236                         rec.append(bytes)
    237                 else:
    238                     raise "Unsupported column type", info.StringData(i)
    239             f.write(repr(tuple(rec))+",\n")
    240         v1.Close()
    241         f.write("]\n\n")
    242     v.Close()
    243     f.write("tables=%s\n" % repr(map(str,tables)))
    244     f.close()
    245 
    246 class _Unspecified:pass
    247 def change_sequence(seq, action, seqno=_Unspecified, cond = _Unspecified):
    248     "Change the sequence number of an action in a sequence list"
    249     for i in range(len(seq)):
    250         if seq[i][0] == action:
    251             if cond is _Unspecified:
    252                 cond = seq[i][1]
    253             if seqno is _Unspecified:
    254                 seqno = seq[i][2]
    255             seq[i] = (action, cond, seqno)
    256             return
    257     raise ValueError, "Action not found in sequence"
    258 
    259 def add_data(db, table, values):
    260     d = MakeInstaller()
    261     v = db.OpenView("SELECT * FROM `%s`" % table)
    262     count = v.ColumnInfo(0).FieldCount
    263     r = d.CreateRecord(count)
    264     for value in values:
    265         assert len(value) == count, value
    266         for i in range(count):
    267             field = value[i]
    268             if isinstance(field, (int, long)):
    269                 r.SetIntegerData(i+1,field)
    270             elif isinstance(field, basestring):
    271                 r.SetStringData(i+1,field)
    272             elif field is None:
    273                 pass
    274             elif isinstance(field, Binary):
    275                 r.SetStream(i+1, field.name)
    276             else:
    277                 raise TypeError, "Unsupported type %s" % field.__class__.__name__
    278         v.Modify(win32com.client.constants.msiViewModifyInsert, r)
    279         r.ClearData()
    280     v.Close()
    281 
    282 def add_stream(db, name, path):
    283     d = MakeInstaller()
    284     v = db.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name)
    285     r = d.CreateRecord(1)
    286     r.SetStream(1, path)
    287     v.Execute(r)
    288     v.Close()
    289 
    290 def init_database(name, schema,
    291                   ProductName, ProductCode, ProductVersion,
    292                   Manufacturer,
    293                   request_uac = False):
    294     try:
    295         os.unlink(name)
    296     except OSError:
    297         pass
    298     ProductCode = ProductCode.upper()
    299     d = MakeInstaller()
    300     # Create the database

    301     db = d.OpenDatabase(name,
    302          win32com.client.constants.msiOpenDatabaseModeCreate)
    303     # Create the tables

    304     for t in schema.tables:
    305         t.create(db)
    306     # Fill the validation table

    307     add_data(db, "_Validation", schema._Validation_records)
    308     # Initialize the summary information, allowing atmost 20 properties

    309     si = db.GetSummaryInformation(20)
    310     si.SetProperty(PID_TITLE, "Installation Database")
    311     si.SetProperty(PID_SUBJECT, ProductName)
    312     si.SetProperty(PID_AUTHOR, Manufacturer)
    313     si.SetProperty(PID_TEMPLATE, msi_type)
    314     si.SetProperty(PID_REVNUMBER, gen_uuid())
    315     if request_uac:
    316         wc = 2 # long file names, compressed, original media

    317     else:
    318         wc = 2 | 8 # +never invoke UAC

    319     si.SetProperty(PID_WORDCOUNT, wc)
    320     si.SetProperty(PID_PAGECOUNT, 200)
    321     si.SetProperty(PID_APPNAME, "Python MSI Library")
    322     # XXX more properties

    323     si.Persist()
    324     add_data(db, "Property", [
    325         ("ProductName", ProductName),
    326         ("ProductCode", ProductCode),
    327         ("ProductVersion", ProductVersion),
    328         ("Manufacturer", Manufacturer),
    329         ("ProductLanguage", "1033")])
    330     db.Commit()
    331     return db
    332 
    333 def add_tables(db, module):
    334     for table in module.tables:
    335         add_data(db, table, getattr(module, table))
    336 
    337 def make_id(str):
    338     #str = str.replace(".", "_") # colons are allowed

    339     str = str.replace(" ", "_")
    340     str = str.replace("-", "_")
    341     str = str.replace("+", "_")
    342     if str[0] in string.digits:
    343         str = "_"+str
    344     assert re.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str
    345     return str
    346 
    347 def gen_uuid():
    348     return str(pythoncom.CreateGuid())
    349 
    350 class CAB:
    351     def __init__(self, name):
    352         self.name = name
    353         self.file = open(name+".txt", "wt")
    354         self.filenames = sets.Set()
    355         self.index = 0
    356 
    357     def gen_id(self, dir, file):
    358         logical = _logical = make_id(file)
    359         pos = 1
    360         while logical in self.filenames:
    361             logical = "%s.%d" % (_logical, pos)
    362             pos += 1
    363         self.filenames.add(logical)
    364         return logical
    365 
    366     def append(self, full, file, logical = None):
    367         if os.path.isdir(full):
    368             return
    369         if not logical:
    370             logical = self.gen_id(dir, file)
    371         self.index += 1
    372         if full.find(" ")!=-1:
    373             print >>self.file, '"%s" %s' % (full, logical)
    374         else:
    375             print >>self.file, '%s %s' % (full, logical)
    376         return self.index, logical
    377 
    378     def commit(self, db):
    379         self.file.close()
    380         try:
    381             os.unlink(self.name+".cab")
    382         except OSError:
    383             pass
    384         for k, v in [(r"Software\Microsoft\VisualStudio\7.1\Setup\VS", "VS7CommonBinDir"),
    385                      (r"Software\Microsoft\VisualStudio\8.0\Setup\VS", "VS7CommonBinDir"),
    386                      (r"Software\Microsoft\VisualStudio\9.0\Setup\VS", "VS7CommonBinDir"),
    387                      (r"Software\Microsoft\Win32SDK\Directories", "Install Dir"),
    388                     ]:
    389             try:
    390                 key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, k)
    391                 dir = _winreg.QueryValueEx(key, v)[0]
    392                 _winreg.CloseKey(key)
    393             except (WindowsError, IndexError):
    394                 continue
    395             cabarc = os.path.join(dir, r"Bin", "cabarc.exe")
    396             if not os.path.exists(cabarc):
    397                 continue
    398             break
    399         else:
    400             print "WARNING: cabarc.exe not found in registry"
    401             cabarc = "cabarc.exe"
    402         cmd = r'"%s" -m lzx:21 n %s.cab @%s.txt' % (cabarc, self.name, self.name)
    403         p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
    404                              stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    405         for line in p.stdout:
    406             if line.startswith("  -- adding "):
    407                 sys.stdout.write(".")
    408             else:
    409                 sys.stdout.write(line)
    410             sys.stdout.flush()
    411         if not os.path.exists(self.name+".cab"):
    412             raise IOError, "cabarc failed"
    413         add_data(db, "Media",
    414                 [(1, self.index, None, "#"+self.name, None, None)])
    415         add_stream(db, self.name, self.name+".cab")
    416         os.unlink(self.name+".txt")
    417         os.unlink(self.name+".cab")
    418         db.Commit()
    419 
    420 _directories = sets.Set()
    421 class Directory:
    422     def __init__(self, db, cab, basedir, physical, _logical, default, componentflags=None):
    423         """Create a new directory in the Directory table. There is a current component
    424         at each point in time for the directory, which is either explicitly created
    425         through start_component, or implicitly when files are added for the first
    426         time. Files are added into the current component, and into the cab file.
    427         To create a directory, a base directory object needs to be specified (can be
    428         None), the path to the physical directory, and a logical directory name.
    429         Default specifies the DefaultDir slot in the directory table. componentflags
    430         specifies the default flags that new components get."""
    431         index = 1
    432         _logical = make_id(_logical)
    433         logical = _logical
    434         while logical in _directories:
    435             logical = "%s%d" % (_logical, index)
    436             index += 1
    437         _directories.add(logical)
    438         self.db = db
    439         self.cab = cab
    440         self.basedir = basedir
    441         self.physical = physical
    442         self.logical = logical
    443         self.component = None
    444         self.short_names = sets.Set()
    445         self.ids = sets.Set()
    446         self.keyfiles = {}
    447         self.componentflags = componentflags
    448         if basedir:
    449             self.absolute = os.path.join(basedir.absolute, physical)
    450             blogical = basedir.logical
    451         else:
    452             self.absolute = physical
    453             blogical = None
    454         add_data(db, "Directory", [(logical, blogical, default)])
    455 
    456     def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None):
    457         """Add an entry to the Component table, and make this component the current for this
    458         directory. If no component name is given, the directory name is used. If no feature
    459         is given, the current feature is used. If no flags are given, the directory's default
    460         flags are used. If no keyfile is given, the KeyPath is left null in the Component
    461         table."""
    462         if flags is None:
    463             flags = self.componentflags
    464         if uuid is None:
    465             uuid = gen_uuid()
    466         else:
    467             uuid = uuid.upper()
    468         if component is None:
    469             component = self.logical
    470         self.component = component
    471         if Win64:
    472             flags |= 256
    473         if keyfile:
    474             keyid = self.cab.gen_id(self.absolute, keyfile)
    475             self.keyfiles[keyfile] = keyid
    476         else:
    477             keyid = None
    478         add_data(self.db, "Component",
    479                         [(component, uuid, self.logical, flags, None, keyid)])
    480         if feature is None:
    481             feature = current_feature
    482         add_data(self.db, "FeatureComponents",
    483                         [(feature.id, component)])
    484 
    485     def make_short(self, file):
    486         file = re.sub(r'[\?|><:/*"+,;=\[\]]', '_', file) # restrictions on short names

    487         parts = file.split(".")
    488         if len(parts)>1:
    489             suffix = parts[-1].upper()
    490         else:
    491             suffix = None
    492         prefix = parts[0].upper()
    493         if len(prefix) <= 8 and (not suffix or len(suffix)<=3):
    494             if suffix:
    495                 file = prefix+"."+suffix
    496             else:
    497                 file = prefix
    498             assert file not in self.short_names
    499         else:
    500             prefix = prefix[:6]
    501             if suffix:
    502                 suffix = suffix[:3]
    503             pos = 1
    504             while 1:
    505                 if suffix:
    506                     file = "%s~%d.%s" % (prefix, pos, suffix)
    507                 else:
    508                     file = "%s~%d" % (prefix, pos)
    509                 if file not in self.short_names: break
    510                 pos += 1
    511                 assert pos < 10000
    512                 if pos in (10, 100, 1000):
    513                     prefix = prefix[:-1]
    514         self.short_names.add(file)
    515         return file
    516 
    517     def add_file(self, file, src=None, version=None, language=None):
    518         """Add a file to the current component of the directory, starting a new one
    519         one if there is no current component. By default, the file name in the source
    520         and the file table will be identical. If the src file is specified, it is
    521         interpreted relative to the current directory. Optionally, a version and a
    522         language can be specified for the entry in the File table."""
    523         if not self.component:
    524             self.start_component(self.logical, current_feature)
    525         if not src:
    526             # Allow relative paths for file if src is not specified

    527             src = file
    528             file = os.path.basename(file)
    529         absolute = os.path.join(self.absolute, src)
    530         assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names

    531         if self.keyfiles.has_key(file):
    532             logical = self.keyfiles[file]
    533         else:
    534             logical = None
    535         sequence, logical = self.cab.append(absolute, file, logical)
    536         assert logical not in self.ids
    537         self.ids.add(logical)
    538         short = self.make_short(file)
    539         full = "%s|%s" % (short, file)
    540         filesize = os.stat(absolute).st_size
    541         # constants.msidbFileAttributesVital

    542         # Compressed omitted, since it is the database default

    543         # could add r/o, system, hidden

    544         attributes = 512
    545         add_data(self.db, "File",
    546                         [(logical, self.component, full, filesize, version,
    547                          language, attributes, sequence)])
    548         if not version:
    549             # Add hash if the file is not versioned

    550             filehash = MakeInstaller().FileHash(absolute, 0)
    551             add_data(self.db, "MsiFileHash",
    552                      [(logical, 0, filehash.IntegerData(1),
    553                        filehash.IntegerData(2), filehash.IntegerData(3),
    554                        filehash.IntegerData(4))])
    555         # Automatically remove .pyc/.pyo files on uninstall (2)

    556         # XXX: adding so many RemoveFile entries makes installer unbelievably

    557         # slow. So instead, we have to use wildcard remove entries

    558         # if file.endswith(".py"):

    559         #     add_data(self.db, "RemoveFile",

    560         #              [(logical+"c", self.component, "%sC|%sc" % (short, file),

    561         #                self.logical, 2),

    562         #               (logical+"o", self.component, "%sO|%so" % (short, file),

    563         #                self.logical, 2)])

    564 
    565     def glob(self, pattern, exclude = None):
    566         """Add a list of files to the current component as specified in the
    567         glob pattern. Individual files can be excluded in the exclude list."""
    568         files = glob.glob1(self.absolute, pattern)
    569         for f in files:
    570             if exclude and f in exclude: continue
    571             self.add_file(f)
    572         return files
    573 
    574     def remove_pyc(self):
    575         "Remove .pyc/.pyo files on uninstall"
    576         add_data(self.db, "RemoveFile",
    577                  [(self.component+"c", self.component, "*.pyc", self.logical, 2),
    578                   (self.component+"o", self.component, "*.pyo", self.logical, 2)])
    579 
    580     def removefile(self, key, pattern):
    581         "Add a RemoveFile entry"
    582         add_data(self.db, "RemoveFile", [(self.component+key, self.component, pattern, self.logical, 2)])
    583 
    584 
    585 class Feature:
    586     def __init__(self, db, id, title, desc, display, level = 1,
    587                  parent=None, directory = None, attributes=0):
    588         self.id = id
    589         if parent:
    590             parent = parent.id
    591         add_data(db, "Feature",
    592                         [(id, parent, title, desc, display,
    593                           level, directory, attributes)])
    594     def set_current(self):
    595         global current_feature
    596         current_feature = self
    597 
    598 class Control:
    599     def __init__(self, dlg, name):
    600         self.dlg = dlg
    601         self.name = name
    602 
    603     def event(self, ev, arg, cond = "1", order = None):
    604         add_data(self.dlg.db, "ControlEvent",
    605                  [(self.dlg.name, self.name, ev, arg, cond, order)])
    606 
    607     def mapping(self, ev, attr):
    608         add_data(self.dlg.db, "EventMapping",
    609                  [(self.dlg.name, self.name, ev, attr)])
    610 
    611     def condition(self, action, condition):
    612         add_data(self.dlg.db, "ControlCondition",
    613                  [(self.dlg.name, self.name, action, condition)])
    614 
    615 class RadioButtonGroup(Control):
    616     def __init__(self, dlg, name, property):
    617         self.dlg = dlg
    618         self.name = name
    619         self.property = property
    620         self.index = 1
    621 
    622     def add(self, name, x, y, w, h, text, value = None):
    623         if value is None:
    624             value = name
    625         add_data(self.dlg.db, "RadioButton",
    626                  [(self.property, self.index, value,
    627                    x, y, w, h, text, None)])
    628         self.index += 1
    629 
    630 class Dialog:
    631     def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel):
    632         self.db = db
    633         self.name = name
    634         self.x, self.y, self.w, self.h = x,y,w,h
    635         add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)])
    636 
    637     def control(self, name, type, x, y, w, h, attr, prop, text, next, help):
    638         add_data(self.db, "Control",
    639                  [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)])
    640         return Control(self, name)
    641 
    642     def text(self, name, x, y, w, h, attr, text):
    643         return self.control(name, "Text", x, y, w, h, attr, None,
    644                      text, None, None)
    645 
    646     def bitmap(self, name, x, y, w, h, text):
    647         return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None)
    648 
    649     def line(self, name, x, y, w, h):
    650         return self.control(name, "Line", x, y, w, h, 1, None, None, None, None)
    651 
    652     def pushbutton(self, name, x, y, w, h, attr, text, next):
    653         return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None)
    654 
    655     def radiogroup(self, name, x, y, w, h, attr, prop, text, next):
    656         add_data(self.db, "Control",
    657                  [(self.name, name, "RadioButtonGroup",
    658                    x, y, w, h, attr, prop, text, next, None)])
    659         return RadioButtonGroup(self, name, prop)
    660 
    661     def checkbox(self, name, x, y, w, h, attr, prop, text, next):
    662         return self.control(name, "CheckBox", x, y, w, h, attr, prop, text, next, None)
    663 
    664 def pe_type(path):
    665     header = open(path, "rb").read(1000)
    666     # offset of PE header is at offset 0x3c

    667     pe_offset = struct.unpack("<i", header[0x3c:0x40])[0]
    668     assert header[pe_offset:pe_offset+4] == "PE\0\0"
    669     machine = struct.unpack("<H", header[pe_offset+4:pe_offset+6])[0]
    670     return machine
    671 
    672 def set_arch_from_file(path):
    673     global msi_type, Win64, arch_ext
    674     machine = pe_type(path)
    675     if machine == 0x14c:
    676         # i386

    677         msi_type = "Intel"
    678         Win64 = 0
    679         arch_ext = ''
    680     elif machine == 0x200:
    681         # Itanium

    682         msi_type = "Intel64"
    683         Win64 = 1
    684         arch_ext = '.ia64'
    685     elif machine == 0x8664:
    686         # AMD64

    687         msi_type = "x64"
    688         Win64 = 1
    689         arch_ext = '.amd64'
    690     else:
    691         raise ValueError, "Unsupported architecture"
    692     msi_type += ";1033"
    693