summaryrefslogtreecommitdiffstats
path: root/Tools/msi/msilib.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/msi/msilib.py')
-rw-r--r--Tools/msi/msilib.py679
1 files changed, 0 insertions, 679 deletions
diff --git a/Tools/msi/msilib.py b/Tools/msi/msilib.py
deleted file mode 100644
index c208b91..0000000
--- a/Tools/msi/msilib.py
+++ /dev/null
@@ -1,679 +0,0 @@
-# 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, _msi
-
-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 at most 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.files = []
- 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
- self.files.append((full, logical))
- return self.index, logical
-
- def commit(self, db):
- try:
- os.unlink(self.name+".cab")
- except OSError:
- pass
- _msi.FCICreate(self.name+".cab", self.files)
- add_data(db, "Media",
- [(1, self.index, None, "#"+self.name, None, None)])
- add_stream(db, self.name, self.name+".cab")
- 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 = {}
- 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
- # initially assume that all files in this directory are unpackaged
- # as files from self.absolute get added, this set is reduced
- self.unpackaged_files = set()
- for f in os.listdir(self.absolute):
- if os.path.isfile(os.path.join(self.absolute, f)):
- self.unpackaged_files.add(f)
- 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):
- long = file
- file = re.sub(r'[\?|><:/*"+,;=\[\]]', '_', file) # restrictions on short names
- parts = file.split(".", 1)
- if len(parts)>1:
- suffix = parts[1].upper()
- else:
- suffix = ''
- prefix = parts[0].upper()
- if len(prefix) <= 8 and '.' not in suffix and len(suffix) <= 3:
- if suffix:
- file = prefix+"."+suffix
- else:
- file = prefix
- assert file not in self.short_names, (file, self.short_names[file])
- else:
- prefix = prefix[:6]
- if suffix:
- # last three characters of last suffix
- suffix = suffix.rsplit('.')[-1][: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[file] = long
- 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
- 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)
- if absolute.startswith(self.absolute):
- # mark file as packaged
- relative = absolute[len(self.absolute)+1:]
- if relative in self.unpackaged_files:
- self.unpackaged_files.remove(relative)
- 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 from __pycache__ on uninstall"
- directory = self.logical + "_pycache"
- add_data(self.db, "Directory", [(directory, self.logical, "__PYCA~1|__pycache__")])
- flags = 256 if Win64 else 0
- add_data(self.db, "Component",
- [(directory, gen_uuid(), directory, flags, None, None)])
- add_data(self.db, "FeatureComponents", [(current_feature.id, directory)])
- add_data(self.db, "CreateFolder", [(directory, directory)])
- add_data(self.db, "RemoveFile",
- [(self.component, self.component, "*.*", directory, 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"