00001 # Microsoft Installer Library 00002 # (C) 2003 Martin v. Loewis 00003 00004 import win32com.client.gencache 00005 import win32com.client 00006 import pythoncom 00007 from win32com.client import constants 00008 import re, string, os, sets, glob, popen2, sys 00009 00010 Win64 = 0 00011 00012 # Partially taken from Wine 00013 datasizemask= 0x00ff 00014 type_valid= 0x0100 00015 type_localizable= 0x0200 00016 00017 typemask= 0x0c00 00018 type_long= 0x0000 00019 type_short= 0x0400 00020 type_string= 0x0c00 00021 type_binary= 0x0800 00022 00023 type_nullable= 0x1000 00024 type_key= 0x2000 00025 # XXX temporary, localizable? 00026 knownbits = datasizemask | type_valid | type_localizable | \ 00027 typemask | type_nullable | type_key 00028 00029 # Summary Info Property IDs 00030 PID_CODEPAGE=1 00031 PID_TITLE=2 00032 PID_SUBJECT=3 00033 PID_AUTHOR=4 00034 PID_KEYWORDS=5 00035 PID_COMMENTS=6 00036 PID_TEMPLATE=7 00037 PID_LASTAUTHOR=8 00038 PID_REVNUMBER=9 00039 PID_LASTPRINTED=11 00040 PID_CREATE_DTM=12 00041 PID_LASTSAVE_DTM=13 00042 PID_PAGECOUNT=14 00043 PID_WORDCOUNT=15 00044 PID_CHARCOUNT=16 00045 PID_APPNAME=18 00046 PID_SECURITY=19 00047 00048 def reset(): 00049 global _directories 00050 _directories = sets.Set() 00051 00052 def EnsureMSI(): 00053 win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}',0x409,1,0) 00054 00055 _Installer=None 00056 def MakeInstaller(): 00057 global _Installer 00058 if _Installer is None: 00059 EnsureMSI() 00060 _Installer = win32com.client.Dispatch('WindowsInstaller.Installer', 00061 resultCLSID='{000C1090-0000-0000-C000-000000000046}') 00062 return _Installer 00063 00064 class Table: 00065 def __init__(self, name): 00066 self.name = name 00067 self.fields = [] 00068 00069 def add_field(self, index, name, type): 00070 self.fields.append((index,name,type)) 00071 00072 def sql(self): 00073 fields = [] 00074 keys = [] 00075 self.fields.sort() 00076 fields = [None]*len(self.fields) 00077 for index, name, type in self.fields: 00078 index -= 1 00079 unk = type & ~knownbits 00080 if unk: 00081 print "%s.%s unknown bits %x" % (self.name, name, unk) 00082 size = type & datasizemask 00083 dtype = type & typemask 00084 if dtype == type_string: 00085 if size: 00086 tname="CHAR(%d)" % size 00087 else: 00088 tname="CHAR" 00089 elif dtype == type_short: 00090 assert size==2 00091 tname = "SHORT" 00092 elif dtype == type_long: 00093 assert size==4 00094 tname="LONG" 00095 elif dtype == type_binary: 00096 assert size==0 00097 tname="OBJECT" 00098 else: 00099 tname="unknown" 00100 print "%s.%sunknown integer type %d" % (self.name, name, size) 00101 if type & type_nullable: 00102 flags = "" 00103 else: 00104 flags = " NOT NULL" 00105 if type & type_localizable: 00106 flags += " LOCALIZABLE" 00107 fields[index] = "`%s` %s%s" % (name, tname, flags) 00108 if type & type_key: 00109 keys.append("`%s`" % name) 00110 fields = ", ".join(fields) 00111 keys = ", ".join(keys) 00112 return "CREATE TABLE %s (%s PRIMARY KEY %s)" % (self.name, fields, keys) 00113 00114 def create(self, db): 00115 v = db.OpenView(self.sql()) 00116 v.Execute(None) 00117 v.Close() 00118 00119 class Binary: 00120 def __init__(self, fname): 00121 self.name = fname 00122 def __repr__(self): 00123 return 'msilib.Binary(os.path.join(dirname,"%s"))' % self.name 00124 00125 def gen_schema(destpath, schemapath): 00126 d = MakeInstaller() 00127 schema = d.OpenDatabase(schemapath, 00128 win32com.client.constants.msiOpenDatabaseModeReadOnly) 00129 00130 # XXX ORBER BY 00131 v=schema.OpenView("SELECT * FROM _Columns") 00132 curtable=None 00133 tables = [] 00134 v.Execute(None) 00135 f = open(destpath, "wt") 00136 f.write("from msilib import Table\n") 00137 while 1: 00138 r=v.Fetch() 00139 if not r:break 00140 name=r.StringData(1) 00141 if curtable != name: 00142 f.write("\n%s = Table('%s')\n" % (name,name)) 00143 curtable = name 00144 tables.append(name) 00145 f.write("%s.add_field(%d,'%s',%d)\n" % 00146 (name, r.IntegerData(2), r.StringData(3), r.IntegerData(4))) 00147 v.Close() 00148 00149 f.write("\ntables=[%s]\n\n" % (", ".join(tables))) 00150 00151 # Fill the _Validation table 00152 f.write("_Validation_records = [\n") 00153 v = schema.OpenView("SELECT * FROM _Validation") 00154 v.Execute(None) 00155 while 1: 00156 r = v.Fetch() 00157 if not r:break 00158 # Table, Column, Nullable 00159 f.write("(%s,%s,%s," % 00160 (`r.StringData(1)`, `r.StringData(2)`, `r.StringData(3)`)) 00161 def put_int(i): 00162 if r.IsNull(i):f.write("None, ") 00163 else:f.write("%d," % r.IntegerData(i)) 00164 def put_str(i): 00165 if r.IsNull(i):f.write("None, ") 00166 else:f.write("%s," % `r.StringData(i)`) 00167 put_int(4) # MinValue 00168 put_int(5) # MaxValue 00169 put_str(6) # KeyTable 00170 put_int(7) # KeyColumn 00171 put_str(8) # Category 00172 put_str(9) # Set 00173 put_str(10)# Description 00174 f.write("),\n") 00175 f.write("]\n\n") 00176 00177 f.close() 00178 00179 def gen_sequence(destpath, msipath): 00180 dir = os.path.dirname(destpath) 00181 d = MakeInstaller() 00182 seqmsi = d.OpenDatabase(msipath, 00183 win32com.client.constants.msiOpenDatabaseModeReadOnly) 00184 00185 v = seqmsi.OpenView("SELECT * FROM _Tables"); 00186 v.Execute(None) 00187 f = open(destpath, "w") 00188 print >>f, "import msilib,os;dirname=os.path.dirname(__file__)" 00189 tables = [] 00190 while 1: 00191 r = v.Fetch() 00192 if not r:break 00193 table = r.StringData(1) 00194 tables.append(table) 00195 f.write("%s = [\n" % table) 00196 v1 = seqmsi.OpenView("SELECT * FROM `%s`" % table) 00197 v1.Execute(None) 00198 info = v1.ColumnInfo(constants.msiColumnInfoTypes) 00199 while 1: 00200 r = v1.Fetch() 00201 if not r:break 00202 rec = [] 00203 for i in range(1,r.FieldCount+1): 00204 if r.IsNull(i): 00205 rec.append(None) 00206 elif info.StringData(i)[0] in "iI": 00207 rec.append(r.IntegerData(i)) 00208 elif info.StringData(i)[0] in "slSL": 00209 rec.append(r.StringData(i)) 00210 elif info.StringData(i)[0]=="v": 00211 size = r.DataSize(i) 00212 bytes = r.ReadStream(i, size, constants.msiReadStreamBytes) 00213 bytes = bytes.encode("latin-1") # binary data represented "as-is" 00214 if table == "Binary": 00215 fname = rec[0]+".bin" 00216 open(os.path.join(dir,fname),"wb").write(bytes) 00217 rec.append(Binary(fname)) 00218 else: 00219 rec.append(bytes) 00220 else: 00221 raise "Unsupported column type", info.StringData(i) 00222 f.write(repr(tuple(rec))+",\n") 00223 v1.Close() 00224 f.write("]\n\n") 00225 v.Close() 00226 f.write("tables=%s\n" % repr(map(str,tables))) 00227 f.close() 00228 00229 def add_data(db, table, values): 00230 print '(add_data).1 :: db=(%s)' % (str(db)) 00231 d = MakeInstaller() 00232 v = db.OpenView("SELECT * FROM `%s`" % table) 00233 count = v.ColumnInfo(0).FieldCount 00234 r = d.CreateRecord(count) 00235 for value in values: 00236 assert len(value) == count, value 00237 for i in range(count): 00238 field = value[i] 00239 if isinstance(field, (int, long)): 00240 r.SetIntegerData(i+1,field) 00241 elif isinstance(field, basestring): 00242 r.SetStringData(i+1,field) 00243 elif field is None: 00244 pass 00245 elif isinstance(field, Binary): 00246 r.SetStream(i+1, field.name) 00247 else: 00248 raise TypeError, "Unsupported type %s" % field.__class__.__name__ 00249 v.Modify(win32com.client.constants.msiViewModifyInsert, r) 00250 r.ClearData() 00251 v.Close() 00252 00253 def add_stream(db, name, path): 00254 d = MakeInstaller() 00255 v = db.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name) 00256 r = d.CreateRecord(1) 00257 r.SetStream(1, path) 00258 v.Execute(r) 00259 v.Close() 00260 00261 def init_database(name, schema, 00262 ProductName, ProductCode, ProductVersion, 00263 Manufacturer): 00264 try: 00265 os.unlink(name) 00266 except OSError: 00267 pass 00268 ProductCode = ProductCode.upper() 00269 d = MakeInstaller() 00270 # Create the database 00271 db = d.OpenDatabase(name, 00272 win32com.client.constants.msiOpenDatabaseModeCreate) 00273 # Create the tables 00274 for t in schema.tables: 00275 t.create(db) 00276 # Fill the validation table 00277 add_data(db, "_Validation", schema._Validation_records) 00278 # Initialize the summary information, allowing atmost 20 properties 00279 si = db.GetSummaryInformation(20) 00280 si.SetProperty(PID_TITLE, "Installation Database") 00281 si.SetProperty(PID_SUBJECT, ProductName) 00282 si.SetProperty(PID_AUTHOR, Manufacturer) 00283 si.SetProperty(PID_TEMPLATE, "Intel;1033") 00284 si.SetProperty(PID_REVNUMBER, ProductCode) # XXX should be package code 00285 si.SetProperty(PID_WORDCOUNT, 2) # long file names, compressed, original media 00286 si.SetProperty(PID_PAGECOUNT, 200) 00287 si.SetProperty(PID_APPNAME, "Python MSI Library") 00288 # XXX more properties 00289 si.Persist() 00290 add_data(db, "Property", [ 00291 ("ProductName", ProductName), 00292 ("ProductCode", ProductCode), 00293 ("ProductVersion", ProductVersion), 00294 ("Manufacturer", Manufacturer), 00295 ("ProductLanguage", "1033")]) 00296 db.Commit() 00297 return db 00298 00299 def add_tables(db, module): 00300 for table in module.tables: 00301 add_data(db, table, getattr(module, table)) 00302 00303 def make_id(str): 00304 str = str.replace(".", "_") # colons are allowed 00305 str = str.replace(" ", "_") 00306 str = str.replace("-", "_") 00307 if str[0] in string.digits: 00308 str = "_"+str 00309 assert re.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str 00310 return str 00311 00312 def gen_uuid(): 00313 return str(pythoncom.CreateGuid()) 00314 00315 class CAB: 00316 def __init__(self, name): 00317 self.name = name 00318 self.file = open(name+".txt", "wt") 00319 self.filenames = sets.Set() 00320 self.index = 0 00321 00322 def gen_id(self, dir, file): 00323 logical = _logical = make_id(file) 00324 pos = 1 00325 while logical in self.filenames: 00326 logical = "%s.%d" % (_logical, pos) 00327 pos += 1 00328 self.filenames.add(logical) 00329 return logical 00330 00331 def append(self, full, file, logical = None): 00332 if os.path.isdir(full): 00333 return 00334 if not logical: 00335 logical = self.gen_id(dir, file) 00336 self.index += 1 00337 if full.find(" ")!=-1: 00338 print >>self.file, '"%s" %s' % (full, logical) 00339 else: 00340 print >>self.file, '%s %s' % (full, logical) 00341 return self.index, logical 00342 00343 def commit(self, db): 00344 self.file.close() 00345 try: 00346 os.unlink(self.name+".cab") 00347 except OSError: 00348 pass 00349 f = popen2.popen4(r"cabarc.exe n %s.cab @%s.txt" % (self.name, self.name))[0] 00350 for line in f: 00351 if line.startswith(" -- adding "): 00352 sys.stdout.write(".") 00353 else: 00354 sys.stdout.write(line) 00355 sys.stdout.flush() 00356 if not os.path.exists(self.name+".cab"): 00357 raise IOError, "cabarc failed" 00358 add_data(db, "Media", [(1, self.index, None, "#"+self.name, None, None)]) 00359 add_stream(db, self.name, self.name+".cab") 00360 os.unlink(self.name+".txt") 00361 os.unlink(self.name+".cab") 00362 db.Commit() 00363 00364 _directories = sets.Set() 00365 class Directory: 00366 def __init__(self, db, cab, basedir, physical, _logical, default): 00367 index = 1 00368 _logical = make_id(_logical) 00369 logical = _logical 00370 while logical in _directories: 00371 logical = "%s%d" % (_logical, index) 00372 index += 1 00373 _directories.add(logical) 00374 self.db = db 00375 self.cab = cab 00376 self.basedir = basedir 00377 self.physical = physical 00378 self.logical = logical 00379 self.component = None 00380 self.short_names = sets.Set() 00381 self.ids = sets.Set() 00382 self.keyfiles = {} 00383 if basedir: 00384 self.absolute = os.path.join(basedir.absolute, physical) 00385 blogical = basedir.logical 00386 else: 00387 self.absolute = physical 00388 blogical = None 00389 add_data(db, "Directory", [(logical, blogical, default)]) 00390 00391 def start_component(self, component, feature = None, keyfile = None): 00392 uuid = gen_uuid() 00393 self.component = component 00394 if Win64: 00395 flags = 256 00396 else: 00397 flags = 0 00398 if keyfile: 00399 keyid = self.cab.gen_id(self.absolute, keyfile) 00400 self.keyfiles[keyfile] = keyid 00401 else: 00402 keyid = None 00403 add_data(self.db, "Component", [(component, uuid, self.logical, flags, None, keyid)]) 00404 if feature is None: 00405 feature = current_feature 00406 add_data(self.db, "FeatureComponents", [(feature.id, component)]) 00407 00408 def make_short(self, file): 00409 parts = file.split(".") 00410 if len(parts)>1: 00411 suffix = parts[-1].upper() 00412 else: 00413 suffix = None 00414 prefix = parts[0].upper() 00415 if len(prefix) <= 8 and (not suffix or len(suffix)<=3): 00416 if suffix: 00417 file = prefix+"."+suffix 00418 else: 00419 file = prefix 00420 try: 00421 assert file not in self.short_names 00422 except Exception, details: 00423 print '(make_short) :: ERROR "%s" because file (%s) is not unique.' % (str(details),file) 00424 else: 00425 prefix = prefix[:6] 00426 if suffix: 00427 suffix = suffix[:3] 00428 pos = 1 00429 while 1: 00430 if suffix: 00431 file = "%s~%d.%s" % (prefix, pos, suffix) 00432 else: 00433 file = "%s~%d" % (prefix, pos) 00434 if file not in self.short_names: break 00435 pos += 1 00436 assert pos < 10000 00437 if pos in (10, 100, 1000): 00438 prefix = prefix[:-1] 00439 self.short_names.add(file) 00440 print '(make_short) :: re.search() :: file=(%s)' % (str(file)) 00441 assert not re.search(r'[\?|><:/*"+,;=\[\]]', file) # restrictions on short names 00442 return file 00443 00444 def add_file(self, file, src=None): 00445 if not self.component: 00446 self.start_component(self.logical, current_feature) 00447 if not src: 00448 # Allow relative paths for file if src is not specified 00449 src = file 00450 file = os.path.basename(file) 00451 absolute = os.path.join(self.absolute, src) 00452 assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names 00453 if self.keyfiles.has_key(file): 00454 logical = self.keyfiles[file] 00455 else: 00456 logical = None 00457 sequence, logical = self.cab.append(absolute, file, logical) 00458 assert logical not in self.ids 00459 self.ids.add(logical) 00460 short = self.make_short(file) 00461 full = "%s|%s" % (short, file) 00462 filesize = os.stat(absolute).st_size 00463 version = None 00464 language = None 00465 # constants.msidbFileAttributesVital 00466 # Compressed omitted, since it is the database default 00467 # could add r/o, system, hidden 00468 attributes = 512 00469 add_data(self.db, "File", 00470 [(logical, self.component, full, filesize, version, 00471 language, attributes, sequence)]) 00472 # Automatically remove .pyc/.pyo files on uninstall (2) 00473 # XXX: adding so many RemoveFile entries makes installer unbelievably 00474 # slow. So instead, we have to use wildcard remove entries 00475 # if file.endswith(".py"): 00476 # add_data(self.db, "RemoveFile", 00477 # [(logical+"c", self.component, "%sC|%sc" % (short, file), 00478 # self.logical, 2), 00479 # (logical+"o", self.component, "%sO|%so" % (short, file), 00480 # self.logical, 2)]) 00481 00482 def glob(self, pattern): 00483 files = glob.glob1(self.absolute, pattern) 00484 for f in files: 00485 self.add_file(f) 00486 return files 00487 00488 def remove_pyc(self): 00489 "Remove .pyc/.pyo files on uninstall" 00490 add_data(self.db, "RemoveFile", 00491 [(self.component+"c", self.component, "*.pyc", self.logical, 2), 00492 (self.component+"o", self.component, "*.pyo", self.logical, 2)]) 00493 00494 class Feature: 00495 def __init__(self, db, id, title, desc, display, level = 1, 00496 parent=None, directory = None): 00497 self.id = id 00498 attributes = 0 00499 if parent: 00500 attributes |= 2 # follow parent 00501 add_data(db, "Feature", 00502 [(id, parent, title, desc, display, 00503 level, directory, attributes)]) 00504 def set_current(self): 00505 global current_feature 00506 current_feature = self 00507 00508 class Control: 00509 def __init__(self, dlg, name): 00510 self.dlg = dlg 00511 self.name = name 00512 00513 def event(self, ev, arg, cond = "1", order = None): 00514 add_data(self.dlg.db, "ControlEvent", 00515 [(self.dlg.name, self.name, ev, arg, cond, order)]) 00516 00517 def mapping(self, ev, attr): 00518 add_data(self.dlg.db, "EventMapping", 00519 [(self.dlg.name, self.name, ev, attr)]) 00520 00521 class RadioButtonGroup(Control): 00522 def __init__(self, dlg, name, property): 00523 self.dlg = dlg 00524 self.name = name 00525 self.property = property 00526 self.index = 1 00527 00528 def add(self, name, x, y, w, h, text): 00529 add_data(self.dlg.db, "RadioButton", 00530 [(self.property, self.index, name, 00531 x, y, w, h, text, None)]) 00532 self.index += 1 00533 00534 class Dialog: 00535 def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel): 00536 self.db = db 00537 self.name = name 00538 self.x, self.y, self.w, self.h = x,y,w,h 00539 add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)]) 00540 00541 def control(self, name, type, x, y, w, h, attr, prop, text, next, help): 00542 add_data(self.db, "Control", 00543 [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)]) 00544 return Control(self, name) 00545 00546 def text(self, name, x, y, w, h, attr, text): 00547 return self.control(name, "Text", x, y, w, h, attr, None, 00548 text, None, None) 00549 00550 def bitmap(self, name, x, y, w, h, text): 00551 return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None) 00552 00553 def line(self, name, x, y, w, h): 00554 return self.control(name, "Line", x, y, w, h, 1, None, None, None, None) 00555 00556 def pushbutton(self, name, x, y, w, h, attr, text, next): 00557 return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None) 00558 00559 def radiogroup(self, name, x, y, w, h, attr, prop, text, next): 00560 add_data(self.db, "Control", 00561 [(self.name, name, "RadioButtonGroup", 00562 x, y, w, h, attr, prop, text, next, None)]) 00563 return RadioButtonGroup(self, name, prop) 00564 00565
© Copyright 2008-2009 Vyper Logix Corp., All Right Reserved; If you reference this document or any part of this document you must use the citation verbatim (including the link) "© Copyright 2008-2009 Vyper Logix Corp., All Right Reserved."
Notice: This source code contained in this document is NOT open source and is NOT being distributed as open source.
122,241 lines of code and growing...