| # Microsoft Installer Library | |
| # (C) 2003 Martin v. Loewis | |
| import win32com.client.gencache | |
| import win32com.client | |
| import pythoncom, pywintypes | |
| from win32com.client import constants | |
| import re, string, os, sets, glob, subprocess, sys, _winreg, struct | |
| try: | |
| basestring | |
| except NameError: | |
| basestring = (str, unicode) | |
| # Partially taken from Wine | |
| datasizemask= 0x00ff | |
| type_valid= 0x0100 | |
| type_localizable= 0x0200 | |
| typemask= 0x0c00 | |
| type_long= 0x0000 | |
| type_short= 0x0400 | |
| type_string= 0x0c00 | |
| type_binary= 0x0800 | |
| type_nullable= 0x1000 | |
| type_key= 0x2000 | |
| # XXX temporary, localizable? | |
| knownbits = datasizemask | type_valid | type_localizable | \ | |
| typemask | type_nullable | type_key | |
| # Summary Info Property IDs | |
| PID_CODEPAGE=1 | |
| PID_TITLE=2 | |
| PID_SUBJECT=3 | |
| PID_AUTHOR=4 | |
| PID_KEYWORDS=5 | |
| PID_COMMENTS=6 | |
| PID_TEMPLATE=7 | |
| PID_LASTAUTHOR=8 | |
| PID_REVNUMBER=9 | |
| PID_LASTPRINTED=11 | |
| PID_CREATE_DTM=12 | |
| PID_LASTSAVE_DTM=13 | |
| PID_PAGECOUNT=14 | |
| PID_WORDCOUNT=15 | |
| PID_CHARCOUNT=16 | |
| PID_APPNAME=18 | |
| PID_SECURITY=19 | |
| def reset(): | |
| global _directories | |
| _directories = sets.Set() | |
| def EnsureMSI(): | |
| win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0) | |
| def EnsureMSM(): | |
| try: | |
| win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 1, 0) | |
| except pywintypes.com_error: | |
| win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 2, 0) | |
| _Installer=None | |
| def MakeInstaller(): | |
| global _Installer | |
| if _Installer is None: | |
| EnsureMSI() | |
| _Installer = win32com.client.Dispatch('WindowsInstaller.Installer', | |
| resultCLSID='{000C1090-0000-0000-C000-000000000046}') | |
| return _Installer | |
| _Merge=None | |
| def MakeMerge2(): | |
| global _Merge | |
| if _Merge is None: | |
| EnsureMSM() | |
| _Merge = win32com.client.Dispatch("Msm.Merge2.1") | |
| return _Merge | |
| class Table: | |
| def __init__(self, name): | |
| self.name = name | |
| self.fields = [] | |
| def add_field(self, index, name, type): | |
| self.fields.append((index,name,type)) | |
| def sql(self): | |
| fields = [] | |
| keys = [] | |
| self.fields.sort() | |
| fields = [None]*len(self.fields) | |
| for index, name, type in self.fields: | |
| index -= 1 | |
| unk = type & ~knownbits | |
| if unk: | |
| print "%s.%s unknown bits %x" % (self.name, name, unk) | |
| size = type & datasizemask | |
| dtype = type & typemask | |
| if dtype == type_string: | |
| if size: | |
| tname="CHAR(%d)" % size | |
| else: | |
| tname="CHAR" | |
| elif dtype == type_short: | |
| assert size==2 | |
| tname = "SHORT" | |
| elif dtype == type_long: | |
| assert size==4 | |
| tname="LONG" | |
| elif dtype == type_binary: | |
| assert size==0 | |
| tname="OBJECT" | |
| else: | |
| tname="unknown" | |
| print "%s.%sunknown integer type %d" % (self.name, name, size) | |
| if type & type_nullable: | |
| flags = "" | |
| else: | |
| flags = " NOT NULL" | |
| if type & type_localizable: | |
| flags += " LOCALIZABLE" | |
| fields[index] = "`%s` %s%s" % (name, tname, flags) | |
| if type & type_key: | |
| keys.append("`%s`" % name) | |
| fields = ", ".join(fields) | |
| keys = ", ".join(keys) | |
| return "CREATE TABLE %s (%s PRIMARY KEY %s)" % (self.name, fields, keys) | |
| def create(self, db): | |
| v = db.OpenView(self.sql()) | |
| v.Execute(None) | |
| v.Close() | |
| class Binary: | |
| def __init__(self, fname): | |
| self.name = fname | |
| def __repr__(self): | |
| return 'msilib.Binary(os.path.join(dirname,"%s"))' % self.name | |
| def gen_schema(destpath, schemapath): | |
| d = MakeInstaller() | |
| schema = d.OpenDatabase(schemapath, | |
| win32com.client.constants.msiOpenDatabaseModeReadOnly) | |
| # XXX ORBER BY | |
| v=schema.OpenView("SELECT * FROM _Columns") | |
| curtable=None | |
| tables = [] | |
| v.Execute(None) | |
| f = open(destpath, "wt") | |
| f.write("from msilib import Table\n") | |
| while 1: | |
| r=v.Fetch() | |
| if not r:break | |
| name=r.StringData(1) | |
| if curtable != name: | |
| f.write("\n%s = Table('%s')\n" % (name,name)) | |
| curtable = name | |
| tables.append(name) | |
| f.write("%s.add_field(%d,'%s',%d)\n" % | |
| (name, r.IntegerData(2), r.StringData(3), r.IntegerData(4))) | |
| v.Close() | |
| f.write("\ntables=[%s]\n\n" % (", ".join(tables))) | |
| # Fill the _Validation table | |
| f.write("_Validation_records = [\n") | |
| v = schema.OpenView("SELECT * FROM _Validation") | |
| v.Execute(None) | |
| while 1: | |
| r = v.Fetch() | |
| if not r:break | |
| # Table, Column, Nullable | |
| f.write("(%s,%s,%s," % | |
| (`r.StringData(1)`, `r.StringData(2)`, `r.StringData(3)`)) | |
| def put_int(i): | |
| if r.IsNull(i):f.write("None, ") | |
| else:f.write("%d," % r.IntegerData(i)) | |
| def put_str(i): | |
| if r.IsNull(i):f.write("None, ") | |
| else:f.write("%s," % `r.StringData(i)`) | |
| put_int(4) # MinValue | |
| put_int(5) # MaxValue | |
| put_str(6) # KeyTable | |
| put_int(7) # KeyColumn | |
| put_str(8) # Category | |
| put_str(9) # Set | |
| put_str(10)# Description | |
| f.write("),\n") | |
| f.write("]\n\n") | |
| f.close() | |
| def gen_sequence(destpath, msipath): | |
| dir = os.path.dirname(destpath) | |
| d = MakeInstaller() | |
| seqmsi = d.OpenDatabase(msipath, | |
| win32com.client.constants.msiOpenDatabaseModeReadOnly) | |
| v = seqmsi.OpenView("SELECT * FROM _Tables"); | |
| v.Execute(None) | |
| f = open(destpath, "w") | |
| print >>f, "import msilib,os;dirname=os.path.dirname(__file__)" | |
| tables = [] | |
| while 1: | |
| r = v.Fetch() | |
| if not r:break | |
| table = r.StringData(1) | |
| tables.append(table) | |
| f.write("%s = [\n" % table) | |
| v1 = seqmsi.OpenView("SELECT * FROM `%s`" % table) | |
| v1.Execute(None) | |
| info = v1.ColumnInfo(constants.msiColumnInfoTypes) | |
| while 1: | |
| r = v1.Fetch() | |
| if not r:break | |
| rec = [] | |
| for i in range(1,r.FieldCount+1): | |
| if r.IsNull(i): | |
| rec.append(None) | |
| elif info.StringData(i)[0] in "iI": | |
| rec.append(r.IntegerData(i)) | |
| elif info.StringData(i)[0] in "slSL": | |
| rec.append(r.StringData(i)) | |
| elif info.StringData(i)[0]=="v": | |
| size = r.DataSize(i) | |
| bytes = r.ReadStream(i, size, constants.msiReadStreamBytes) | |
| bytes = bytes.encode("latin-1") # binary data represented "as-is" | |
| if table == "Binary": | |
| fname = rec[0]+".bin" | |
| open(os.path.join(dir,fname),"wb").write(bytes) | |
| rec.append(Binary(fname)) | |
| else: | |
| rec.append(bytes) | |
| else: | |
| raise "Unsupported column type", info.StringData(i) | |
| f.write(repr(tuple(rec))+",\n") | |
| v1.Close() | |
| f.write("]\n\n") | |
| v.Close() | |
| f.write("tables=%s\n" % repr(map(str,tables))) | |
| f.close() | |
| class _Unspecified:pass | |
| def change_sequence(seq, action, seqno=_Unspecified, cond = _Unspecified): | |
| "Change the sequence number of an action in a sequence list" | |
| for i in range(len(seq)): | |
| if seq[i][0] == action: | |
| if cond is _Unspecified: | |
| cond = seq[i][1] | |
| if seqno is _Unspecified: | |
| seqno = seq[i][2] | |
| seq[i] = (action, cond, seqno) | |
| return | |
| raise ValueError, "Action not found in sequence" | |
| def add_data(db, table, values): | |
| d = MakeInstaller() | |
| v = db.OpenView("SELECT * FROM `%s`" % table) | |
| count = v.ColumnInfo(0).FieldCount | |
| r = d.CreateRecord(count) | |
| for value in values: | |
| assert len(value) == count, value | |
| for i in range(count): | |
| field = value[i] | |
| if isinstance(field, (int, long)): | |
| r.SetIntegerData(i+1,field) | |
| elif isinstance(field, basestring): | |
| r.SetStringData(i+1,field) | |
| elif field is None: | |
| pass | |
| elif isinstance(field, Binary): | |
| r.SetStream(i+1, field.name) | |
| else: | |
| raise TypeError, "Unsupported type %s" % field.__class__.__name__ | |
| v.Modify(win32com.client.constants.msiViewModifyInsert, r) | |
| r.ClearData() | |
| v.Close() | |
| def add_stream(db, name, path): | |
| d = MakeInstaller() | |
| v = db.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name) | |
| r = d.CreateRecord(1) | |
| r.SetStream(1, path) | |
| v.Execute(r) | |
| v.Close() | |
| def init_database(name, schema, | |
| ProductName, ProductCode, ProductVersion, | |
| Manufacturer, | |
| request_uac = False): | |
| try: | |
| os.unlink(name) | |
| except OSError: | |
| pass | |
| ProductCode = ProductCode.upper() | |
| d = MakeInstaller() | |
| # Create the database | |
| db = d.OpenDatabase(name, | |
| win32com.client.constants.msiOpenDatabaseModeCreate) | |
| # Create the tables | |
| for t in schema.tables: | |
| t.create(db) | |
| # Fill the validation table | |
| add_data(db, "_Validation", schema._Validation_records) | |
| # Initialize the summary information, allowing atmost 20 properties | |
| si = db.GetSummaryInformation(20) | |
| si.SetProperty(PID_TITLE, "Installation Database") | |
| si.SetProperty(PID_SUBJECT, ProductName) | |
| si.SetProperty(PID_AUTHOR, Manufacturer) | |
| si.SetProperty(PID_TEMPLATE, msi_type) | |
| si.SetProperty(PID_REVNUMBER, gen_uuid()) | |
| if request_uac: | |
| wc = 2 # long file names, compressed, original media | |
| else: | |
| wc = 2 | 8 # +never invoke UAC | |
| si.SetProperty(PID_WORDCOUNT, wc) | |
| si.SetProperty(PID_PAGECOUNT, 200) | |
| si.SetProperty(PID_APPNAME, "Python MSI Library") | |
| # XXX more properties | |
| si.Persist() | |
| add_data(db, "Property", [ | |
| ("ProductName", ProductName), | |
| ("ProductCode", ProductCode), | |
| ("ProductVersion", ProductVersion), | |
| ("Manufacturer", Manufacturer), | |
| ("ProductLanguage", "1033")]) | |
| db.Commit() | |
| return db | |
| def add_tables(db, module): | |
| for table in module.tables: | |
| add_data(db, table, getattr(module, table)) | |
| def make_id(str): | |
| #str = str.replace(".", "_") # colons are allowed | |
| str = str.replace(" ", "_") | |
| str = str.replace("-", "_") | |
| str = str.replace("+", "_") | |
| if str[0] in string.digits: | |
| str = "_"+str | |
| assert re.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str | |
| return str | |
| def gen_uuid(): | |
| return str(pythoncom.CreateGuid()) | |
| class CAB: | |
| def __init__(self, name): | |
| self.name = name | |
| self.file = open(name+".txt", "wt") | |
| self.filenames = sets.Set() | |
| self.index = 0 | |
| def gen_id(self, dir, file): | |
| logical = _logical = make_id(file) | |
| pos = 1 | |
| while logical in self.filenames: | |
| logical = "%s.%d" % (_logical, pos) | |
| pos += 1 | |
| self.filenames.add(logical) | |
| return logical | |
| def append(self, full, file, logical = None): | |
| if os.path.isdir(full): | |
| return | |
| if not logical: | |
| logical = self.gen_id(dir, file) | |
| self.index += 1 | |
| if full.find(" ")!=-1: | |
| print >>self.file, '"%s" %s' % (full, logical) | |
| else: | |
| print >>self.file, '%s %s' % (full, logical) | |
| return self.index, logical | |
| def commit(self, db): | |
| self.file.close() | |
| try: | |
| os.unlink(self.name+".cab") | |
| except OSError: | |
| pass | |
| for k, v in [(r"Software\Microsoft\VisualStudio\7.1\Setup\VS", "VS7CommonBinDir"), | |
| (r"Software\Microsoft\VisualStudio\8.0\Setup\VS", "VS7CommonBinDir"), | |
| (r"Software\Microsoft\VisualStudio\9.0\Setup\VS", "VS7CommonBinDir"), | |
| (r"Software\Microsoft\Win32SDK\Directories", "Install Dir"), | |
| ]: | |
| try: | |
| key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, k) | |
| dir = _winreg.QueryValueEx(key, v)[0] | |
| _winreg.CloseKey(key) | |
| except (WindowsError, IndexError): | |
| continue | |
| cabarc = os.path.join(dir, r"Bin", "cabarc.exe") | |
| if not os.path.exists(cabarc): | |
| continue | |
| break | |
| else: | |
| print "WARNING: cabarc.exe not found in registry" | |
| cabarc = "cabarc.exe" | |
| cmd = r'"%s" -m lzx:21 n %s.cab @%s.txt' % (cabarc, self.name, self.name) | |
| p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | |
| for line in p.stdout: | |
| if line.startswith(" -- adding "): | |
| sys.stdout.write(".") | |
| else: | |
| sys.stdout.write(line) | |
| sys.stdout.flush() | |
| if not os.path.exists(self.name+".cab"): | |
| raise IOError, "cabarc failed" | |
| add_data(db, "Media", | |
| [(1, self.index, None, "#"+self.name, None, None)]) | |
| add_stream(db, self.name, self.name+".cab") | |
| os.unlink(self.name+".txt") | |
| os.unlink(self.name+".cab") | |
| db.Commit() | |
| _directories = sets.Set() | |
| class Directory: | |
| def __init__(self, db, cab, basedir, physical, _logical, default, componentflags=None): | |
| """Create a new directory in the Directory table. There is a current component | |
| at each point in time for the directory, which is either explicitly created | |
| through start_component, or implicitly when files are added for the first | |
| time. Files are added into the current component, and into the cab file. | |
| To create a directory, a base directory object needs to be specified (can be | |
| None), the path to the physical directory, and a logical directory name. | |
| Default specifies the DefaultDir slot in the directory table. componentflags | |
| specifies the default flags that new components get.""" | |
| index = 1 | |
| _logical = make_id(_logical) | |
| logical = _logical | |
| while logical in _directories: | |
| logical = "%s%d" % (_logical, index) | |
| index += 1 | |
| _directories.add(logical) | |
| self.db = db | |
| self.cab = cab | |
| self.basedir = basedir | |
| self.physical = physical | |
| self.logical = logical | |
| self.component = None | |
| self.short_names = sets.Set() | |
| self.ids = sets.Set() | |
| self.keyfiles = {} | |
| self.componentflags = componentflags | |
| if basedir: | |
| self.absolute = os.path.join(basedir.absolute, physical) | |
| blogical = basedir.logical | |
| else: | |
| self.absolute = physical | |
| blogical = None | |
| add_data(db, "Directory", [(logical, blogical, default)]) | |
| def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None): | |
| """Add an entry to the Component table, and make this component the current for this | |
| directory. If no component name is given, the directory name is used. If no feature | |
| is given, the current feature is used. If no flags are given, the directory's default | |
| flags are used. If no keyfile is given, the KeyPath is left null in the Component | |
| table.""" | |
| if flags is None: | |
| flags = self.componentflags | |
| if uuid is None: | |
| uuid = gen_uuid() | |
| else: | |
| uuid = uuid.upper() | |
| if component is None: | |
| component = self.logical | |
| self.component = component | |
| if Win64: | |
| flags |= 256 | |
| if keyfile: | |
| keyid = self.cab.gen_id(self.absolute, keyfile) | |
| self.keyfiles[keyfile] = keyid | |
| else: | |
| keyid = None | |
| add_data(self.db, "Component", | |
| [(component, uuid, self.logical, flags, None, keyid)]) | |
| if feature is None: | |
| feature = current_feature | |
| add_data(self.db, "FeatureComponents", | |
| [(feature.id, component)]) | |
| def make_short(self, file): | |
| file = re.sub(r'[\?|><:/*"+,;=\[\]]', '_', file) # restrictions on short names | |
| parts = file.split(".") | |
| if len(parts)>1: | |
| suffix = parts[-1].upper() | |
| else: | |
| suffix = None | |
| prefix = parts[0].upper() | |
| if len(prefix) <= 8 and (not suffix or len(suffix)<=3): | |
| if suffix: | |
| file = prefix+"."+suffix | |
| else: | |
| file = prefix | |
| assert file not in self.short_names | |
| else: | |
| prefix = prefix[:6] | |
| if suffix: | |
| suffix = suffix[:3] | |
| pos = 1 | |
| while 1: | |
| if suffix: | |
| file = "%s~%d.%s" % (prefix, pos, suffix) | |
| else: | |
| file = "%s~%d" % (prefix, pos) | |
| if file not in self.short_names: break | |
| pos += 1 | |
| assert pos < 10000 | |
| if pos in (10, 100, 1000): | |
| prefix = prefix[:-1] | |
| self.short_names.add(file) | |
| return file | |
| def add_file(self, file, src=None, version=None, language=None): | |
| """Add a file to the current component of the directory, starting a new one | |
| one if there is no current component. By default, the file name in the source | |
| and the file table will be identical. If the src file is specified, it is | |
| interpreted relative to the current directory. Optionally, a version and a | |
| language can be specified for the entry in the File table.""" | |
| if not self.component: | |
| self.start_component(self.logical, current_feature) | |
| if not src: | |
| # Allow relative paths for file if src is not specified | |
| src = file | |
| file = os.path.basename(file) | |
| absolute = os.path.join(self.absolute, src) | |
| assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names | |
| if self.keyfiles.has_key(file): | |
| logical = self.keyfiles[file] | |
| else: | |
| logical = None | |
| sequence, logical = self.cab.append(absolute, file, logical) | |
| assert logical not in self.ids | |
| self.ids.add(logical) | |
| short = self.make_short(file) | |
| full = "%s|%s" % (short, file) | |
| filesize = os.stat(absolute).st_size | |
| # constants.msidbFileAttributesVital | |
| # Compressed omitted, since it is the database default | |
| # could add r/o, system, hidden | |
| attributes = 512 | |
| add_data(self.db, "File", | |
| [(logical, self.component, full, filesize, version, | |
| language, attributes, sequence)]) | |
| if not version: | |
| # Add hash if the file is not versioned | |
| filehash = MakeInstaller().FileHash(absolute, 0) | |
| add_data(self.db, "MsiFileHash", | |
| [(logical, 0, filehash.IntegerData(1), | |
| filehash.IntegerData(2), filehash.IntegerData(3), | |
| filehash.IntegerData(4))]) | |
| # Automatically remove .pyc/.pyo files on uninstall (2) | |
| # XXX: adding so many RemoveFile entries makes installer unbelievably | |
| # slow. So instead, we have to use wildcard remove entries | |
| # if file.endswith(".py"): | |
| # add_data(self.db, "RemoveFile", | |
| # [(logical+"c", self.component, "%sC|%sc" % (short, file), | |
| # self.logical, 2), | |
| # (logical+"o", self.component, "%sO|%so" % (short, file), | |
| # self.logical, 2)]) | |
| def glob(self, pattern, exclude = None): | |
| """Add a list of files to the current component as specified in the | |
| glob pattern. Individual files can be excluded in the exclude list.""" | |
| files = glob.glob1(self.absolute, pattern) | |
| for f in files: | |
| if exclude and f in exclude: continue | |
| self.add_file(f) | |
| return files | |
| def remove_pyc(self): | |
| "Remove .pyc/.pyo files on uninstall" | |
| add_data(self.db, "RemoveFile", | |
| [(self.component+"c", self.component, "*.pyc", self.logical, 2), | |
| (self.component+"o", self.component, "*.pyo", self.logical, 2)]) | |
| def removefile(self, key, pattern): | |
| "Add a RemoveFile entry" | |
| add_data(self.db, "RemoveFile", [(self.component+key, self.component, pattern, self.logical, 2)]) | |
| class Feature: | |
| def __init__(self, db, id, title, desc, display, level = 1, | |
| parent=None, directory = None, attributes=0): | |
| self.id = id | |
| if parent: | |
| parent = parent.id | |
| add_data(db, "Feature", | |
| [(id, parent, title, desc, display, | |
| level, directory, attributes)]) | |
| def set_current(self): | |
| global current_feature | |
| current_feature = self | |
| class Control: | |
| def __init__(self, dlg, name): | |
| self.dlg = dlg | |
| self.name = name | |
| def event(self, ev, arg, cond = "1", order = None): | |
| add_data(self.dlg.db, "ControlEvent", | |
| [(self.dlg.name, self.name, ev, arg, cond, order)]) | |
| def mapping(self, ev, attr): | |
| add_data(self.dlg.db, "EventMapping", | |
| [(self.dlg.name, self.name, ev, attr)]) | |
| def condition(self, action, condition): | |
| add_data(self.dlg.db, "ControlCondition", | |
| [(self.dlg.name, self.name, action, condition)]) | |
| class RadioButtonGroup(Control): | |
| def __init__(self, dlg, name, property): | |
| self.dlg = dlg | |
| self.name = name | |
| self.property = property | |
| self.index = 1 | |
| def add(self, name, x, y, w, h, text, value = None): | |
| if value is None: | |
| value = name | |
| add_data(self.dlg.db, "RadioButton", | |
| [(self.property, self.index, value, | |
| x, y, w, h, text, None)]) | |
| self.index += 1 | |
| class Dialog: | |
| def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel): | |
| self.db = db | |
| self.name = name | |
| self.x, self.y, self.w, self.h = x,y,w,h | |
| add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)]) | |
| def control(self, name, type, x, y, w, h, attr, prop, text, next, help): | |
| add_data(self.db, "Control", | |
| [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)]) | |
| return Control(self, name) | |
| def text(self, name, x, y, w, h, attr, text): | |
| return self.control(name, "Text", x, y, w, h, attr, None, | |
| text, None, None) | |
| def bitmap(self, name, x, y, w, h, text): | |
| return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None) | |
| def line(self, name, x, y, w, h): | |
| return self.control(name, "Line", x, y, w, h, 1, None, None, None, None) | |
| def pushbutton(self, name, x, y, w, h, attr, text, next): | |
| return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None) | |
| def radiogroup(self, name, x, y, w, h, attr, prop, text, next): | |
| add_data(self.db, "Control", | |
| [(self.name, name, "RadioButtonGroup", | |
| x, y, w, h, attr, prop, text, next, None)]) | |
| return RadioButtonGroup(self, name, prop) | |
| def checkbox(self, name, x, y, w, h, attr, prop, text, next): | |
| return self.control(name, "CheckBox", x, y, w, h, attr, prop, text, next, None) | |
| def pe_type(path): | |
| header = open(path, "rb").read(1000) | |
| # offset of PE header is at offset 0x3c | |
| pe_offset = struct.unpack("<i", header[0x3c:0x40])[0] | |
| assert header[pe_offset:pe_offset+4] == "PE\0\0" | |
| machine = struct.unpack("<H", header[pe_offset+4:pe_offset+6])[0] | |
| return machine | |
| def set_arch_from_file(path): | |
| global msi_type, Win64, arch_ext | |
| machine = pe_type(path) | |
| if machine == 0x14c: | |
| # i386 | |
| msi_type = "Intel" | |
| Win64 = 0 | |
| arch_ext = '' | |
| elif machine == 0x200: | |
| # Itanium | |
| msi_type = "Intel64" | |
| Win64 = 1 | |
| arch_ext = '.ia64' | |
| elif machine == 0x8664: | |
| # AMD64 | |
| msi_type = "x64" | |
| Win64 = 1 | |
| arch_ext = '.amd64' | |
| else: | |
| raise ValueError, "Unsupported architecture" | |
| msi_type += ";1033" |