/// <summary> /// Merges in any modules to the output database. /// </summary> /// <param name="tempDatabaseFile">The temporary database file.</param> /// <param name="output">Output that specifies database and modules to merge.</param> /// <param name="fileRows">The indexed file rows.</param> /// <param name="suppressedTableNames">The names of tables that are suppressed.</param> /// <remarks>Expects that output's database has already been generated.</remarks> private void MergeModules(string tempDatabaseFile, Output output, FileRowCollection fileRows, StringCollection suppressedTableNames) { Debug.Assert(OutputType.Product == output.Type); Table wixMergeTable = output.Tables["WixMerge"]; Table wixFeatureModulesTable = output.Tables["WixFeatureModules"]; // check for merge rows to see if there is any work to do if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count) { return; } IMsmMerge2 merge = null; bool commit = true; bool logOpen = false; bool databaseOpen = false; string logPath = null; try { merge = NativeMethods.GetMsmMerge(); logPath = Path.Combine(this.TempFilesLocation, "merge.log"); merge.OpenLog(logPath); logOpen = true; merge.OpenDatabase(tempDatabaseFile); databaseOpen = true; // process all the merge rows foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows) { bool moduleOpen = false; try { short mergeLanguage; try { mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); } catch (System.FormatException) { this.core.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); continue; } this.core.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); moduleOpen = true; // If there is merge configuration data, create a callback object to contain it all. ConfigurationCallback callback = null; if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData)) { callback = new ConfigurationCallback(wixMergeRow.ConfigurationData); } // merge the module into the database that's being built this.core.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile)); merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback); // connect any non-primary features if (null != wixFeatureModulesTable) { foreach (Row row in wixFeatureModulesTable.Rows) { if (wixMergeRow.Id == (string)row[1]) { this.core.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0])); merge.Connect((string)row[0]); } } } } catch (COMException) { commit = false; } finally { IMsmErrors mergeErrors = merge.Errors; // display all the errors encountered during the merge operations for this module for (int i = 1; i <= mergeErrors.Count; i++) { IMsmError mergeError = mergeErrors[i]; StringBuilder databaseKeys = new StringBuilder(); StringBuilder moduleKeys = new StringBuilder(); // build a string of the database keys for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++) { if (1 != j) { databaseKeys.Append(';'); } databaseKeys.Append(mergeError.DatabaseKeys[j]); } // build a string of the module keys for (int j = 1; j <= mergeError.ModuleKeys.Count; j++) { if (1 != j) { moduleKeys.Append(';'); } moduleKeys.Append(mergeError.ModuleKeys[j]); } // display the merge error based on the msm error type switch (mergeError.Type) { case MsmErrorType.msmErrorExclusion: this.core.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString())); break; case MsmErrorType.msmErrorFeatureRequired: this.core.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id)); break; case MsmErrorType.msmErrorLanguageFailed: this.core.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); break; case MsmErrorType.msmErrorLanguageUnsupported: this.core.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); break; case MsmErrorType.msmErrorResequenceMerge: this.core.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); break; case MsmErrorType.msmErrorTableMerge: if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table { this.core.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); } break; case MsmErrorType.msmErrorPlatformMismatch: this.core.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); break; default: this.core.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); break; } } if (0 >= mergeErrors.Count && !commit) { this.core.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace)); } if (moduleOpen) { merge.CloseModule(); } } } } finally { if (databaseOpen) { merge.CloseDatabase(commit); } if (logOpen) { merge.CloseLog(); } } // stop processing if an error previously occurred if (this.core.EncounteredError) { return; } using (Database db = new Database(tempDatabaseFile, OpenDatabase.Direct)) { Table suppressActionTable = output.Tables["WixSuppressAction"]; // suppress individual actions if (null != suppressActionTable) { foreach (Row row in suppressActionTable.Rows) { if (db.TableExists((string)row[0])) { string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); using (View view = db.OpenExecuteView(query)) { using (Record record = view.Fetch()) { if (null != record) { this.core.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); view.Modify(ModifyView.Delete, record); } } } } } } // query for merge module actions in suppressed sequences and drop them foreach (string tableName in suppressedTableNames) { if (!db.TableExists(tableName)) { continue; } using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) { while (true) { using (Record resultRecord = view.Fetch()) { if (null == resultRecord) { break; } this.core.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); } } } // drop suppressed sequences using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) { } // delete the validation rows using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) { using (Record record = new Record(1)) { record.SetString(1, tableName); view.Execute(record); } } } // now update the Attributes column for the files from the Merge Modules this.core.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) { foreach (FileRow fileRow in fileRows) { if (!fileRow.FromModule) { continue; } using (Record record = new Record(1)) { record.SetString(1, fileRow.File); view.Execute(record); } using (Record recordUpdate = view.Fetch()) { if (null == recordUpdate) { throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module."); } recordUpdate.SetInteger(1, fileRow.Sequence); // update the file attributes to match the compression specified // on the Merge element or on the Package element int attributes = 0; // get the current value if its not null if (!recordUpdate.IsNull(2)) { attributes = recordUpdate.GetInteger(2); } if (YesNoType.Yes == fileRow.Compressed) { // these are mutually exclusive attributes |= MsiInterop.MsidbFileAttributesCompressed; attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; } else if (YesNoType.No == fileRow.Compressed) { // these are mutually exclusive attributes |= MsiInterop.MsidbFileAttributesNoncompressed; attributes &= ~MsiInterop.MsidbFileAttributesCompressed; } else // not specified { Debug.Assert(YesNoType.NotSet == fileRow.Compressed); // clear any compression bits attributes &= ~MsiInterop.MsidbFileAttributesCompressed; attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; } recordUpdate.SetInteger(2, attributes); view.Modify(ModifyView.Update, recordUpdate); } } } db.Commit(); } }
public override void Run(List<string> args) { // args[0]=v, args[1]=filename.msi if (args.Count < 2) throw new OptionException("You must specify an msi filename.", "v"); var msiFileName = args[1]; using (var msidb = new Database(msiFileName, OpenDatabase.ReadOnly)) { const string tableName = "Property"; var query = string.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName); using (var view = new ViewWrapper(msidb.OpenExecuteView(query))) { foreach (var row in view.Records) { var property = (string)row[view.ColumnIndex("Property")]; var value = row[view.ColumnIndex("Value")]; if (string.Equals("ProductVersion", property, StringComparison.InvariantCultureIgnoreCase)) { Console.WriteLine(value); return; } } Console.WriteLine("Version not found!"); } } }
public static TableRow[] GetRowsFromTable(Database msidb, string tableName) { if (!msidb.TableExists(tableName)) { Trace.WriteLine(string.Format("Table name does {0} not exist Found.", tableName)); return new TableRow[0]; } string query = string.Concat("SELECT * FROM `", tableName, "`"); using (var view = new ViewWrapper(msidb.OpenExecuteView(query))) { var /*<TableRow>*/ rows = new ArrayList(view.Records.Count); ColumnInfo[] columns = view.Columns; foreach (object[] values in view.Records) { HybridDictionary valueCollection = new HybridDictionary(values.Length); for (int cIndex = 0; cIndex < columns.Length; cIndex++) { valueCollection[columns[cIndex].Name] = values[cIndex]; } rows.Add(new TableRow(valueCollection)); } return (TableRow[]) rows.ToArray(typeof(TableRow)); } }
public override void Run(List<string> args) { /* examples: * lessmsi l -t Component c:\theinstall.msi * lessmsi l -t Property c:\theinstall.msi */ args = args.Skip(1).ToList(); var tableName = ""; var options = new OptionSet { { "t=", "Specifies the table to list.", t => tableName = t } }; var extra = options.Parse(args); if (extra.Count < 1) throw new OptionException("You must specify the msi file to list from.", "l"); if (string.IsNullOrEmpty(tableName)) throw new OptionException("You must specify the table name to list.", "t"); var csv = new StringBuilder(); Debug.Print("Opening msi file '{0}'.", extra[0]); using (var msidb = new Database(extra[0], OpenDatabase.ReadOnly)) { Debug.Print("Opening table '{0}'.", tableName); var query = string.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName); using (var view = new ViewWrapper(msidb.OpenExecuteView(query))) { for (var index = 0; index < view.Columns.Length; index++) { var col = view.Columns[index]; if (index > 0) csv.Append(','); csv.Append(col.Name); } csv.AppendLine(); foreach (var row in view.Records) { for (var colIndex = 0; colIndex < row.Length; colIndex++) { if (colIndex > 0) csv.Append(','); var val = Convert.ToString(row[colIndex], CultureInfo.InvariantCulture); var newLine = Environment.NewLine; string[] requireEscapeChars = { ",", newLine }; Array.ForEach(requireEscapeChars, s => { if (val.Contains(s)) val = "\"" + val + "\""; }); csv.Append(val); } csv.AppendLine(); } } } Console.Write(csv.ToString()); }
/// <summary> /// Write the Cab to disk. /// </summary> /// <param name="filePath">Specifies the path to the file to contain the stream.</param> /// <param name="cabName">Specifies the name of the file in the stream.</param> public static void ExtractCabFromPackage(string filePath, string cabName, Database inputDatabase) { using (View view = inputDatabase.OpenExecuteView(String.Concat("SELECT * FROM `_Streams` WHERE `Name` = '", cabName, "'"))) { Record record; if (view.Fetch(out record)) { FileStream cabFilestream = null; BinaryWriter writer = null; try { cabFilestream = new FileStream(filePath, FileMode.Create); // Create the writer for data. writer = new BinaryWriter(cabFilestream); var buf = new byte[1024*1024]; int count; do { const int MsiInterop_Storages_Data = 2; //From wiX:Index to column name Data into Record for row in Msi Table Storages count = record.GetStream(MsiInterop_Storages_Data, buf, buf.Length); if (count > 0) writer.Write(buf, 0, count); } while (count > 0); } finally { if (writer != null) { writer.Close(); } if (cabFilestream != null) { cabFilestream.Close(); } } } } }
/// <summary> /// Process uncompressed files. /// </summary> /// <param name="tempDatabaseFile">The temporary database file.</param> /// <param name="fileRows">The collection of files to copy into the image.</param> /// <param name="fileTransfers">Array of files to be transfered.</param> /// <param name="mediaRows">The indexed media rows.</param> /// <param name="layoutDirectory">The directory in which the image should be layed out.</param> /// <param name="compressed">Flag if source image should be compressed.</param> /// <param name="longNamesInImage">Flag if long names should be used.</param> private void ProcessUncompressedFiles(string tempDatabaseFile, FileRowCollection fileRows, ArrayList fileTransfers, MediaRowCollection mediaRows, string layoutDirectory, bool compressed, bool longNamesInImage) { if (0 == fileRows.Count || this.core.EncounteredError) { return; } Hashtable directories = new Hashtable(); using (Database db = new Database(tempDatabaseFile, OpenDatabase.ReadOnly)) { using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) { while (true) { using (Record directoryRecord = directoryView.Fetch()) { if (null == directoryRecord) { break; } string sourceName = Installer.GetName(directoryRecord.GetString(3), true, longNamesInImage); directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); } } } using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) { using (Record fileQueryRecord = new Record(1)) { // for each file in the array of uncompressed files foreach (FileRow fileRow in fileRows) { string relativeFileLayoutPath = null; string mediaLayoutDirectory = this.FileManager.ResolveMedia(mediaRows[fileRow.DiskId], layoutDirectory); // setup up the query record and find the appropriate file in the // previously executed file view fileQueryRecord[1] = fileRow.File; fileView.Execute(fileQueryRecord); using (Record fileRecord = fileView.Fetch()) { if (null == fileRecord) { throw new WixException(WixErrors.FileIdentifierNotFound(fileRow.SourceLineNumbers, fileRow.File)); } relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], compressed, longNamesInImage); } // finally put together the base media layout path and the relative file layout path string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); FileTransfer transfer; if (FileTransfer.TryCreate(fileRow.Source, fileLayoutPath, false, "File", fileRow.SourceLineNumbers, out transfer)) { fileTransfers.Add(transfer); } } } } } }
/// <summary> /// Retrieve files and their information from merge modules. /// </summary> /// <param name="output">Internal representation of the msi database to operate upon.</param> /// <param name="fileRows">The indexed file rows.</param> private void ProcessMergeModules(Output output, FileRowCollection fileRows) { Table wixMergeTable = output.Tables["WixMerge"]; if (null != wixMergeTable) { IMsmMerge2 merge = NativeMethods.GetMsmMerge(); // Get the output's minimum installer version int outputInstallerVersion = int.MinValue; Table summaryInformationTable = output.Tables["_SummaryInformation"]; if (null != summaryInformationTable) { foreach (Row row in summaryInformationTable.Rows) { if (14 == (int)row[0]) { outputInstallerVersion = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); break; } } } foreach (Row row in wixMergeTable.Rows) { bool containsFiles = false; WixMergeRow wixMergeRow = (WixMergeRow)row; try { // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) { if (db.TableExists("File") && db.TableExists("Component")) { Hashtable uniqueModuleFileIdentifiers = System.Collections.Specialized.CollectionsUtil.CreateCaseInsensitiveHashtable(); using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) { // add each file row from the merge module into the file row collection (check for errors along the way) while (true) { using (Record record = view.Fetch()) { if (null == record) { break; } // NOTE: this is very tricky - the merge module file rows are not added to the // file table because they should not be created via idt import. Instead, these // rows are created by merging in the actual modules FileRow fileRow = new FileRow(null, this.core.TableDefinitions["File"]); fileRow.File = record[1]; fileRow.Compressed = wixMergeRow.FileCompression; fileRow.Directory = record[2]; fileRow.DiskId = wixMergeRow.DiskId; fileRow.FromModule = true; fileRow.PatchGroup = -1; fileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat), Path.DirectorySeparatorChar, record[1]); FileRow collidingFileRow = fileRows[fileRow.File]; FileRow collidingModuleFileRow = (FileRow)uniqueModuleFileIdentifiers[fileRow.File]; if (null == collidingFileRow && null == collidingModuleFileRow) { fileRows.Add(fileRow); // keep track of file identifiers in this merge module uniqueModuleFileIdentifiers.Add(fileRow.File, fileRow); } else // collision(s) detected { // case-sensitive collision with another merge module or a user-authored file identifier if (null != collidingFileRow) { this.core.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFileRow.File)); } // case-insensitive collision with another file identifier in the same merge module if (null != collidingModuleFileRow) { this.core.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, fileRow.File, collidingModuleFileRow.File)); } } containsFiles = true; } } } } // Get the summary information to detect the Schema using (SummaryInformation summaryInformation = new SummaryInformation(db)) { string moduleInstallerVersionString = summaryInformation.GetProperty(14); try { int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); if (moduleInstallerVersion > outputInstallerVersion) { this.core.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, outputInstallerVersion)); } } catch (FormatException) { throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); } } } } catch (FileNotFoundException) { throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); } catch (Win32Exception) { throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile)); } // if the module has files and creating layout if (containsFiles && !this.suppressLayout) { bool moduleOpen = false; short mergeLanguage; try { mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); } catch (System.FormatException) { this.core.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); continue; } try { merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); moduleOpen = true; string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat); // extract the module cabinet, then explode all of the files to a temp directory string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab"); merge.ExtractCAB(moduleCabPath); string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId); Directory.CreateDirectory(mergeIdPath); using (WixExtractCab extractCab = new WixExtractCab()) { try { extractCab.Extract(moduleCabPath, mergeIdPath); } catch (FileNotFoundException) { throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); } catch { throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); } } } catch (COMException ce) { throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); } finally { if (moduleOpen) { merge.CloseModule(); } } } } } }
/// <summary> /// Updates database with signatures from external cabinets. /// </summary> /// <param name="databaseFile">Path to MSI database.</param> /// <param name="outputFile">Ouput for updated MSI database.</param> /// <param name="tidy">Clean up files.</param> /// <returns>True if database is updated.</returns> public bool InscribeDatabase(string databaseFile, string outputFile, bool tidy) { // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered bool foundUnsignedExternals = false; bool shouldCommit = false; FileAttributes attributes = File.GetAttributes(databaseFile); if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) { this.OnMessage(WixErrors.ReadOnlyOutputFile(databaseFile)); return shouldCommit; } using (Database database = new Database(databaseFile, OpenDatabase.Transact)) { // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content int codepage = 1252; // list of certificates for this database (hash/identifier) Dictionary<string, string> certificates = new Dictionary<string, string>(); // Reset the in-memory tables for this new database Table digitalSignatureTable = new Table(null, this.tableDefinitions["MsiDigitalSignature"]); Table digitalCertificateTable = new Table(null, this.tableDefinitions["MsiDigitalCertificate"]); // If any digital signature records exist that are not of the media type, preserve them if (database.TableExists("MsiDigitalSignature")) { using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) { while (true) { using (Record digitalSignatureRecord = digitalSignatureView.Fetch()) { if (null == digitalSignatureRecord) { break; } Row digitalSignatureRow = null; digitalSignatureRow = digitalSignatureTable.CreateRow(null); string table = digitalSignatureRecord.GetString(0); string signObject = digitalSignatureRecord.GetString(1); digitalSignatureRow[0] = table; digitalSignatureRow[1] = signObject; digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); if (false == digitalSignatureRecord.IsNull(3)) { // Export to a file, because the MSI API's require us to provide a file path on disk string hashPath = Path.Combine(this.TempFilesLocation, "MsiDigitalSignature"); string hashFileName = string.Concat(table,".", signObject, ".bin"); Directory.CreateDirectory(hashPath); hashPath = Path.Combine(hashPath, hashFileName); using (FileStream fs = File.Create(hashPath)) { int bytesRead; byte[] buffer = new byte[1024 * 4]; while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } digitalSignatureRow[3] = hashFileName; } } } } } // If any digital certificates exist, extract and preserve them if (database.TableExists("MsiDigitalCertificate")) { using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) { while (true) { using (Record digitalCertificateRecord = digitalCertificateView.Fetch()) { if (null == digitalCertificateRecord) { break; } string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate // Export to a file, because the MSI API's require us to provide a file path on disk string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); Directory.CreateDirectory(certPath); certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer")); using (FileStream fs = File.Create(certPath)) { int bytesRead; byte[] buffer = new byte[1024 * 4]; while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } // Add it to our "add to MsiDigitalCertificate" table dictionary Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); digitalCertificateRow[0] = certificateId; // Now set the file path on disk where this binary stream will be picked up at import time digitalCertificateRow[1] = string.Concat(certificateId, ".cer"); // Load the cert to get it's thumbprint X509Certificate cert = X509Certificate.CreateFromCertFile(certPath); X509Certificate2 cert2 = new X509Certificate2(cert); certificates.Add(cert2.Thumbprint, certificateId); } } } } using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) { while (true) { using (Record mediaRecord = mediaView.Fetch()) { if (null == mediaRecord) { break; } X509Certificate2 cert2 = null; Row digitalSignatureRow = null; string cabName = mediaRecord.GetString(4); // get the name of the cab // If there is no cabinet or it's an internal cab, skip it. if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) { continue; } string cabId = mediaRecord.GetString(1); // get the ID of the cab string cabPath = Path.Combine(Path.GetDirectoryName(databaseFile), cabName); // If the cabs aren't there, throw an error but continue to catch the other errors if (!File.Exists(cabPath)) { this.OnMessage(WixErrors.WixFileNotFound(cabPath)); continue; } try { // Get the certificate from the cab X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); cert2 = new X509Certificate2(signedFileCert); } catch (System.Security.Cryptography.CryptographicException e) { uint HResult = unchecked((uint)Marshal.GetHRForException(e)); // If the file has no cert, continue, but flag that we found at least one so we can later give a warning if (0x80092009 == HResult) // CRYPT_E_NO_MATCH { foundUnsignedExternals = true; continue; } // todo: exactly which HRESULT corresponds to this issue? // If it's one of these exact platforms, warn the user that it may be due to their OS. if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP { this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); } else // otherwise, generic error { this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); } } // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added if (!certificates.ContainsKey(cert2.Thumbprint)) { // generate a stable identifier string certificateGeneratedId = Common.GenerateIdentifier("cer", true, cert2.Thumbprint); // Add it to our "add to MsiDigitalCertificate" table dictionary Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); digitalCertificateRow[0] = certificateGeneratedId; // Export to a file, because the MSI API's require us to provide a file path on disk string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); Directory.CreateDirectory(certPath); certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer")); File.Delete(certPath); using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) { writer.Write(cert2.RawData); writer.Close(); } // Now set the file path on disk where this binary stream will be picked up at import time digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer"); certificates.Add(cert2.Thumbprint, certificateGeneratedId); } digitalSignatureRow = digitalSignatureTable.CreateRow(null); digitalSignatureRow[0] = "Media"; digitalSignatureRow[1] = cabId; digitalSignatureRow[2] = certificates[cert2.Thumbprint]; } } } if (digitalCertificateTable.Rows.Count > 0) { database.ImportTable(codepage, (IMessageHandler)this, digitalCertificateTable, this.TempFilesLocation, true); shouldCommit = true; } if (digitalSignatureTable.Rows.Count > 0) { database.ImportTable(codepage, (IMessageHandler)this, digitalSignatureTable, this.TempFilesLocation, true); shouldCommit = true; } // TODO: if we created the table(s), then we should add the _Validation records for them. certificates = null; // If we did find external cabs but none of them were signed, give a warning if (foundUnsignedExternals) { this.OnMessage(WixWarnings.ExternalCabsAreNotSigned(databaseFile)); } if (shouldCommit) { database.Commit(); } } return shouldCommit; }
public void LoadTables() { var allTableNames = new string[] { #region Hard Coded Table Names //FYI: This list is from http://msdn.microsoft.com/en-us/library/2k3te2cs%28VS.100%29.aspx "ActionText", "AdminExecuteSequence ", "AdminUISequence", "AdvtExecuteSequence", "AdvtUISequence", "AppId", "AppSearch", "BBControl", "Billboard", "Binary", "BindImage", "CCPSearch", "CheckBox", "Class", "ComboBox", "CompLocator", "Complus", "Component", "Condition", "Control", "ControlCondition", "ControlEvent", "CreateFolder", "CustomAction", "Dialog", "Directory", "DrLocator", "DuplicateFile", "Environment", "Error", "EventMapping", "Extension", "Feature", "FeatureComponents", "File", "FileSFPCatalog", "Font", "Icon", "IniFile", "IniLocator", "InstallExecuteSequence", "InstallUISequence", "IsolatedComponent", "LaunchCondition", "ListBox", "ListView", "LockPermissions", "Media", "MIME", "MoveFile", "MsiAssembly", "MsiAssemblyName", "MsiDigitalCertificate", "MsiDigitalSignature", "MsiEmbeddedChainer", "MsiEmbeddedUI", "MsiFileHash", "MsiLockPermissionsEx Table", "MsiPackageCertificate", "MsiPatchCertificate", "MsiPatchHeaders", "MsiPatchMetadata", "MsiPatchOldAssemblyName", "MsiPatchOldAssemblyFile", "MsiPatchSequence", "MsiServiceConfig", "MsiServiceConfigFailureActions", "MsiSFCBypass", "ODBCAttribute", "ODBCDataSource", "ODBCDriver", "ODBCSourceAttribute", "ODBCTranslator", "Patch", "PatchPackage", "ProgId", "Property", "PublishComponent", "RadioButton", "Registry", "RegLocator", "RemoveFile", "RemoveIniFile", "RemoveRegistry", "ReserveCost", "SelfReg", "ServiceControl", "ServiceInstall", "SFPCatalog", "Shortcut", "Signature", "TextStyle", "TypeLib", "UIText", "Verb", "_Validation", "_Columns", "_Streams", "_Storages", "_Tables", "_TransformView Table", "Upgrade" #endregion }; var systemTables = new string[] { "_Validation", "_Columns", "_Streams", "_Storages", "_Tables", "_TransformView Table" }; IEnumerable<string> msiTableNames = allTableNames; using (var msidb = new Database(View.SelectedMsiFile.FullName, OpenDatabase.ReadOnly)) { using (new DisposableCursor(View)) { try { Status("Loading list of tables..."); var query = "SELECT * FROM `_Tables`"; using (var msiTable = new ViewWrapper(msidb.OpenExecuteView(query))) { var tableNames = from record in msiTable.Records select record[0] as string; //NOTE: system tables are not usually in the _Tables table. var tempList = tableNames.ToList(); tempList.AddRange(systemTables); msiTableNames = tempList.ToArray(); } Status(""); } catch (Exception e) { Status(e.Message); } View.cboTable.Items.Clear(); View.cboTable.Items.AddRange(msiTableNames.ToArray()); View.cboTable.SelectedIndex = 0; } } }
static void Main(string[] args) { foreach (string arg in args) { string [] argSplit = arg.Split(new char[] {':'}, 2); if (argSplit.Length != 2) Usage(); argSplit[0] = argSplit[0].Substring(1).ToLower(); if (argSplit[0] == "msi") { installer = argSplit[1]; } else if (argSplit[0] == "out") { outDir = argSplit[1]; } else { Usage(); } } if (installer == null) Usage(); Database db = new Database(installer, OpenDatabase.ReadOnly); View view = db.OpenExecuteView("SELECT FileName FROM File"); Record record; while (view.Fetch(out record)) { string file = record[1]; //parse file by |?? string[] fileParts = file.Split('|'); string name; if (fileParts.Length == 2) { name = fileParts[1].ToLower(); } else if (fileParts.Length == 1) { name = fileParts[0].ToLower(); } else { continue; } string ext = Path.GetExtension(name); if (ext == ".dll" || ext == ".exe") { if (!binFiles.Contains(name)) { binFiles.Add(name); Console.WriteLine(name); } } } if (outDir == null) Environment.Exit(0); if (!Directory.Exists(outDir)) { Directory.CreateDirectory(outDir); } if (!Directory.Exists(outDir + "\\client")) { Directory.CreateDirectory(outDir + "\\client"); } if (!Directory.Exists(outDir + "\\server")) { Directory.CreateDirectory(outDir + "\\server"); } string clientDir = Environment.GetEnvironmentVariable("BUILD_TREE_CLIENT") + "\\dll"; string serverDir = Environment.GetEnvironmentVariable("BUILD_TREE_SERVER") + "\\dll"; if (!Directory.Exists(clientDir)) { Console.WriteLine("Could not find {0}. Did you set the environment", clientDir); Environment.Exit(2); } if (!Directory.Exists(serverDir)) { Console.WriteLine("Could not find {0}. Did you set the environment", serverDir); Environment.Exit(2); } foreach (string binFile in binFiles) { bool copyClient = false; if (File.Exists(clientDir + "\\" + binFile)) { copyClient = true; File.Copy(clientDir + "\\" + binFile, outDir + "\\client\\" + binFile); } if (File.Exists(serverDir + "\\" + binFile)) { if (copyClient) { ConsoleColor fore = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Warning: {0} exists in both the client and server directories", binFile); Console.ForegroundColor = fore; } File.Copy(serverDir + "\\" + binFile, outDir + "\\server\\" + binFile); } } }
/// <summary> /// Adds all the streams to the final output. /// </summary> /// <param name="databasePath">Path to database.</param> /// <param name="output">Output object that points at final output.</param> private void ImportStreams(string databasePath, Output output) { using (Database db = new Database(databasePath, OpenDatabase.Direct)) { View streamsView = null; View binaryView = null; View iconView = null; View certificateView = null; try { streamsView = db.OpenExecuteView("SELECT `Name`, `Data` FROM `_Streams`"); if (db.TableExists("Binary")) { binaryView = db.OpenExecuteView("SELECT `Name`, `Data` FROM `Binary`"); } if (db.TableExists("Icon")) { iconView = db.OpenExecuteView("SELECT `Name`, `Data` FROM `Icon`"); } if (db.TableExists("MsiDigitalCertificate")) { certificateView = db.OpenExecuteView("SELECT `DigitalCertificate`, `CertData` FROM `MsiDigitalCertificate`"); } foreach (ImportStream importStream in output.ImportStreams) { string src; using (Record record = new Record(2)) { try { switch (importStream.Type) { case ImportStreamType.Cabinet: this.OnMessage(WixVerboses.ImportCabinetStream(importStream.StreamName, importStream.Path)); record[1] = importStream.StreamName; record.SetStream(2, importStream.Path); streamsView.Modify(ModifyView.Assign, record); break; case ImportStreamType.DigitalCertificate: src = this.extension.FileResolutionHandler(importStream.Path, FileResolutionType.DigitalCertificate); this.OnMessage(WixVerboses.ImportDigitalCertificateStream(null, VerboseLevel.Trace, src)); record[1] = importStream.StreamName; record.SetStream(2, importStream.Path); certificateView.Modify(ModifyView.Assign, record); break; case ImportStreamType.Binary: src = this.extension.FileResolutionHandler(importStream.Path, FileResolutionType.Binary); this.OnMessage(WixVerboses.ImportBinaryStream(null, VerboseLevel.Trace, src)); if (OutputType.Module == output.Type) { record[1] = String.Concat(importStream.StreamName, ".", output.ModularizationGuid); } else { record[1] = importStream.StreamName; } if (55 < record[1].Length) { throw new WixInvalidAttributeException(null, "Binary", "Id", String.Format("Identifier cannot be longer than 55 characters. Binary identifier: {0}", record[1])); } record.SetStream(2, src); binaryView.Modify(ModifyView.Assign, record); break; case ImportStreamType.Icon: src = this.extension.FileResolutionHandler(importStream.Path, FileResolutionType.Icon); this.OnMessage(WixVerboses.ImportIconStream(null, VerboseLevel.Verbose, src)); if (OutputType.Module == output.Type) { int start = importStream.StreamName.LastIndexOf("."); if (-1 == start) { record[1] = String.Concat(importStream.StreamName, ".", output.ModularizationGuid); } else { record[1] = String.Concat(importStream.StreamName.Substring(0, start), ".", output.ModularizationGuid, importStream.StreamName.Substring(start)); } } else { record[1] = importStream.StreamName; } if (55 < record[1].Length) { throw new WixInvalidAttributeException(null, "Icon", "Id", String.Format("Identifier cannot be longer than 55 characters. Icon identifier: {0}", record[1])); } record.SetStream(2, src); iconView.Modify(ModifyView.Assign, record); break; default: throw new ArgumentException(String.Format("unknown import stream type: {0}, name: {1}", importStream.Type, importStream.StreamName), "importStream"); } } catch (WixFileNotFoundException wfnfe) { this.OnMessage(WixErrors.BinderExtensionMissingFile(null, ErrorLevel.Normal, wfnfe.Message)); } } } db.Commit(); } catch (FileNotFoundException fnfe) { throw new WixFileNotFoundException(null, fnfe.FileName, fnfe); } finally { if (null != certificateView) { certificateView.Close(); } if (null != iconView) { iconView.Close(); } if (null != binaryView) { binaryView.Close(); } if (null != streamsView) { streamsView.Close(); } } } }
/// <summary> /// Lays out the binaries for the uncompressed portion of a source image. /// </summary> /// <param name="databasePath">Path to database.</param> /// <param name="output">Output being created.</param> /// <param name="files">Array of files to copy into image.</param> /// <param name="packageCompressed">Flag if package is compressed.</param> /// <param name="fileTransfers">Array of files to be transfered.</param> private void CreateUncompressedImage(string databasePath, Output output, ArrayList files, bool packageCompressed, ArrayList fileTransfers) { if (0 == files.Count || this.foundError) { return; } bool longNamesInImage = output.LongFileNames; Hashtable directories = new Hashtable(); using (Database db = new Database(databasePath, OpenDatabase.ReadOnly)) { using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) { Record directoryRecord; while (directoryView.Fetch(out directoryRecord)) { string sourceName = GetSourceName(directoryRecord.GetString(3), longNamesInImage); directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); } } using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) { // if an output path was specified for our image, use that as our default base, // otherwise use the directory where the output is being placed string defaultBaseOuputPath = null != this.imagebaseOutputPath ? this.imagebaseOutputPath : Path.GetDirectoryName(output.Path); using (Record fileQueryRecord = new Record(1)) { // for each file in the array of uncompressed files foreach (FileMediaInformation fmi in files) { string currentSourcePath = null; string relativeSourcePath = null; // determine what the base of the file should be. If there was // no src specified in the Media element (the default) then just // use the default output path (usually the same directory as the // output file). If there was a build directory specified then // check if it is a absolute path, and if not add the default // output path to the root MediaRow mediaRow = output.MediaRows[fmi.Media]; string baseRelativeSourcePath = mediaRow.Layout; if (null == baseRelativeSourcePath) { baseRelativeSourcePath = defaultBaseOuputPath; } else if (!Path.IsPathRooted(baseRelativeSourcePath)) { baseRelativeSourcePath = Path.Combine(defaultBaseOuputPath, baseRelativeSourcePath); } // setup up the query record and find the appropriate file in the // previously executed file view fileQueryRecord[1] = fmi.File; fileView.Execute(fileQueryRecord); Record fileRecord; if (!fileView.Fetch(out fileRecord)) { throw new WixFileMediaInformationKeyNotFoundException(fmi.File); } string fileName = GetSourceName(fileRecord[2], longNamesInImage); if (packageCompressed) { // use just the file name of the file since all uncompressed files must appear // in the root of the image in a compressed package relativeSourcePath = fileName; } else { // get the relative path of where we want the source to be as specified // in the Directory table string directoryPath = GetDirectoryPath(directories, fileRecord[1], longNamesInImage); relativeSourcePath = Path.Combine(directoryPath, fileName); } // if the relative source path was not resolved above then we have to bail if (null == relativeSourcePath) { throw new WixFileMediaInformationKeyNotFoundException(fmi.File); } // strip off "SourceDir" if it's still on there if (relativeSourcePath.StartsWith("SourceDir\\")) { relativeSourcePath = relativeSourcePath.Substring(10); } // resolve the src path for the file and ensure it exists try { currentSourcePath = this.extension.FileResolutionHandler(fmi.Source, FileResolutionType.File); } catch (WixFileNotFoundException wfnfe) { this.OnMessage(WixErrors.BinderExtensionMissingFile(null, ErrorLevel.Normal, wfnfe.Message)); continue; } if (!(File.Exists(currentSourcePath))) { this.OnMessage(WixErrors.CannotFindFile(null, ErrorLevel.Normal, fmi.FileId, fmi.File, fmi.Source)); continue; } // finally put together the base image output path and the resolved source path string resolvedSourcePath = Path.Combine(baseRelativeSourcePath, relativeSourcePath); // if the current source path (where we know that the file already exists) and the resolved // path as dictated by the Directory table are not the same, then propagate the file. The // image that we create may have already been done by some other process other than the linker, so // there is no reason to copy the files to the resolved source if they are already there. if (0 != String.Compare(Path.GetFullPath(currentSourcePath), Path.GetFullPath(resolvedSourcePath), true)) { // just put the file in the transfers array, how anti-climatic fileTransfers.Add(new FileTransfer(currentSourcePath, resolvedSourcePath, false)); } } } } } }
/// <summary> /// Update several msi tables with data contained in files references in the File table. /// </summary> /// <remarks> /// For versioned files, update the file version and language in the File table. For /// unversioned files, add a row to the MsiFileHash table for the file. For assembly /// files, add a row to the MsiAssembly table and add AssemblyName information by adding /// MsiAssemblyName rows. /// </remarks> /// <param name="output">Internal representation of the msi database to operate upon.</param> private void UpdateFileInformation(Output output) { OutputTable mergeTable = output.OutputTables["Merge"]; if (null != mergeTable) { foreach (OutputRow outputRow in mergeTable.OutputRows) { MergeRow mergeRow = (MergeRow)outputRow.Row; string moduleFile = null; try { moduleFile = this.extension.FileResolutionHandler(mergeRow.SourceFile, FileResolutionType.Module); } catch (WixFileNotFoundException wfnfe) { this.OnMessage(WixErrors.BinderExtensionMissingFile(null, ErrorLevel.Normal, wfnfe.Message)); continue; } output.Modules.Add(mergeRow); try { // read the module's File table to get its FileMediaInformation entries using (Database db = new Database(moduleFile, OpenDatabase.ReadOnly)) { mergeRow.HasFiles = false; if (db.TableExists("File") && db.TableExists("Component")) { Hashtable uniqueModuleFileIdentifiers = System.Collections.Specialized.CollectionsUtil.CreateCaseInsensitiveHashtable(); using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) { Record record; while (view.Fetch(out record)) { FileMediaInformation fileMediaInformation = new FileMediaInformation(record[1], record[2], mergeRow.DiskId, String.Concat(this.tempFiles.BasePath, Path.DirectorySeparatorChar, "MergeId.", mergeRow.Id.GetHashCode().ToString("X4", CultureInfo.InvariantCulture.NumberFormat), Path.DirectorySeparatorChar, record[1]), mergeRow.Number, mergeRow.FileCompression, moduleFile, -1); FileMediaInformation otherFileMediaInformation = output.FileMediaInformationCollection[fileMediaInformation.FileId]; string collidingModuleFileIdentifier = (string)uniqueModuleFileIdentifiers[fileMediaInformation.FileId]; if (null == otherFileMediaInformation && null == collidingModuleFileIdentifier) { output.FileMediaInformationCollection.Add(fileMediaInformation); // keep track of file identifiers in this merge module uniqueModuleFileIdentifiers.Add(fileMediaInformation.FileId, fileMediaInformation.FileId); } else // collision(s) detected { // case-sensitive collision with another merge module or a user-authored file identifier if (null != otherFileMediaInformation) { this.OnMessage(WixErrors.DuplicateModuleFileIdentifier(mergeRow.SourceLineNumbers, mergeRow.Id, fileMediaInformation.FileId)); } // case-insensitive collision with another file identifier in the same merge module if (null != collidingModuleFileIdentifier) { this.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(mergeRow.SourceLineNumbers, mergeRow.Id, fileMediaInformation.FileId, collidingModuleFileIdentifier)); } } mergeRow.HasFiles = true; } } } } } catch (FileNotFoundException fnfe) { throw new WixFileNotFoundException(null, moduleFile, fnfe); } catch (IOException ioe) { throw new WixMergeModuleOpenException(mergeRow.SourceLineNumbers, mergeRow.Id, moduleFile, ioe); } } } // calculate sequence numbers and media disk id layout for all file media information objects if (OutputType.Module == output.Type) { int lastSequence = 0; foreach (FileMediaInformation fmi in output.FileMediaInformationCollection) { fmi.Modularize(output.ModularizationGuid); fmi.Sequence = ++lastSequence; } } else { int lastSequence = 0; MediaRow mediaRow = null; output.FileMediaInformationCollection.Sort(); SortedList patchGroups = new SortedList(); // sequence the non-patch-added files foreach (FileMediaInformation fmi in output.FileMediaInformationCollection) { if (null == mediaRow) { mediaRow = output.MediaRows[fmi.Media]; } else if (mediaRow.DiskId != fmi.Media) { mediaRow.LastSequence = lastSequence; mediaRow = output.MediaRows[fmi.Media]; } if (0 < fmi.PatchGroup) { ArrayList patchGroup = (ArrayList)patchGroups[fmi.PatchGroup]; if (null == patchGroup) { patchGroup = new ArrayList(); patchGroups.Add(fmi.PatchGroup, patchGroup); } patchGroup.Add(fmi); } else { fmi.Sequence = ++lastSequence; } } if (null != mediaRow) { mediaRow.LastSequence = lastSequence; mediaRow = null; } // sequence the patch-added files foreach (ArrayList patchGroup in patchGroups.Values) { foreach (FileMediaInformation fmi in patchGroup) { if (null == mediaRow) { mediaRow = output.MediaRows[fmi.Media]; } else if (mediaRow.DiskId != fmi.Media) { mediaRow.LastSequence = lastSequence; mediaRow = output.MediaRows[fmi.Media]; } fmi.Sequence = ++lastSequence; } } if (null != mediaRow) { mediaRow.LastSequence = lastSequence; } } // copy the special media rows back to the real media table if (0 < output.MediaRows.Count) { OutputTable mediaTable = Common.EnsureOutputTable(output, this.tableDefinitions["Media"]); foreach (MediaRow mediaRow in output.MediaRows) { mediaTable.OutputRows.Add(new OutputRow(mediaRow)); } } OutputTable fileTable = output.OutputTables["File"]; if (null == fileTable) // no work to do { return; } foreach (OutputRow outputRow in fileTable.OutputRows) { FileRow fileRow = outputRow.Row as FileRow; if (null == fileRow) { throw new ApplicationException("Expected FileRow"); } // copy the sequence number from the file media information to the file table fileRow.Sequence = output.FileMediaInformationCollection[fileRow.File].Sequence; string src = fileRow.Source; FileInfo fileInfo = null; if (!this.suppressFileHashAndInfo || (!this.suppressAssemblies && FileAssemblyType.NotAnAssembly != fileRow.AssemblyType)) { try { src = this.extension.FileResolutionHandler(fileRow.Source, FileResolutionType.File); } catch (WixFileNotFoundException wfnfe) { this.OnMessage(WixErrors.BinderExtensionMissingFile(fileRow.SourceLineNumbers, ErrorLevel.Normal, wfnfe.Message)); continue; } try { fileInfo = new FileInfo(src); } catch (ArgumentException) { this.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, ErrorLevel.Normal, src)); continue; } catch (PathTooLongException) { this.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, ErrorLevel.Normal, src)); continue; } catch (NotSupportedException) { this.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, ErrorLevel.Normal, src)); continue; } } if (!this.suppressFileHashAndInfo) { if (fileInfo.Exists) { string version; string language; fileRow.FileSize = fileInfo.Length; try { MsiBase.FileVersion(fileInfo.FullName, out version, out language); } catch (FileNotFoundException e) { throw new WixFileNotFoundException(null, fileInfo.FullName, e); // TODO: find a way to get the sourceFile (instead of null) } if (0 == version.Length && 0 == language.Length) // unversioned files have their hashes added to the MsiFileHash table { int[] hash; try { MsiBase.GetFileHash(fileInfo.FullName, 0, out hash); } catch (FileNotFoundException e) { throw new WixFileNotFoundException(null, fileInfo.FullName, e); // TODO: find a way to get the sourceFile (instead of null) } OutputTable outputHashTable = Common.EnsureOutputTable(output, this.tableDefinitions["MsiFileHash"]); Row hashRow = new Row(outputHashTable.TableDefinition); hashRow[0] = fileRow.File; hashRow[1] = 0; hashRow[2] = hash[0]; hashRow[3] = hash[1]; hashRow[4] = hash[2]; hashRow[5] = hash[3]; outputHashTable.OutputRows.Add(new OutputRow(hashRow)); } else // update the file row with the version and language information { fileRow.Version = version; fileRow.Language = language; } } else { this.OnMessage(WixErrors.CannotFindFile(fileRow.SourceLineNumbers, ErrorLevel.Normal, fileRow.File, fileRow.FileName, src)); } } // if we're not suppressing automagically grabbing assembly information and this is a // CLR assembly, load the assembly and get the assembly name information if (!this.suppressAssemblies) { if (FileAssemblyType.DotNetAssembly == fileRow.AssemblyType) { StringDictionary assemblyNameValues = new StringDictionary(); // under CLR 2.0, use a more robust method of gathering AssemblyName information if (2 <= Environment.Version.Major) { CLRInterop.IReferenceIdentity referenceIdentity = null; Guid referenceIdentityGuid = CLRInterop.ReferenceIdentityGuid; if (0 == CLRInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity)) { if (null != referenceIdentity) { string culture = referenceIdentity.GetAttribute(null, "Culture"); if (null != culture) { assemblyNameValues.Add("Culture", culture); } string name = referenceIdentity.GetAttribute(null, "Name"); if (null != name) { assemblyNameValues.Add("Name", name); } string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture"); if (null != processorArchitecture) { assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture); } string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken"); if (null != publicKeyToken) { assemblyNameValues.Add("PublicKeyToken", publicKeyToken.ToUpper(CultureInfo.InvariantCulture)); } string version = referenceIdentity.GetAttribute(null, "Version"); if (null != version) { assemblyNameValues.Add("Version", version); } } } } else { AssemblyName assemblyName = null; try { assemblyName = AssemblyName.GetAssemblyName(fileInfo.FullName); if (null != assemblyName.CultureInfo) { assemblyNameValues.Add("Culture", assemblyName.CultureInfo.ToString()); } if (null != assemblyName.Name) { assemblyNameValues.Add("Name", assemblyName.Name); } byte[] publicKey = assemblyName.GetPublicKeyToken(); if (null != publicKey && 0 < publicKey.Length) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < publicKey.GetLength(0); ++i) { sb.AppendFormat("{0:X2}", publicKey[i]); } assemblyNameValues.Add("PublicKeyToken", sb.ToString()); } if (null != assemblyName.Version) { assemblyNameValues.Add("Version", assemblyName.Version.ToString()); } } catch (FileNotFoundException fnfe) { throw new WixFileNotFoundException(fileRow.SourceLineNumbers, fileInfo.FullName, fnfe); } catch (Exception e) { if (e is NullReferenceException || e is SEHException) { throw; } else { throw new WixInvalidAssemblyException(fileRow.SourceLineNumbers, fileInfo, e); } } } OutputTable assemblyNameOutputTable = Common.EnsureOutputTable(output, this.tableDefinitions["MsiAssemblyName"]); if (assemblyNameValues.ContainsKey("name")) { this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "name", assemblyNameValues["name"]); } string fileVersion = null; if (this.setMsiAssemblyNameFileVersion) { string language; MsiBase.FileVersion(fileInfo.FullName, out fileVersion, out language); this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "fileVersion", fileVersion); } if (assemblyNameValues.ContainsKey("version")) { string assemblyVersion = assemblyNameValues["version"]; // there is a bug in fusion that requires the assembly's "version" attribute // to be equal to or longer than the "fileVersion" in length when its present; // the workaround is to prepend zeroes to the last version number in the assembly version if (this.setMsiAssemblyNameFileVersion && null != fileVersion && fileVersion.Length > assemblyVersion.Length) { string padding = new string('0', fileVersion.Length - assemblyVersion.Length); string[] assemblyVersionNumbers = assemblyVersion.Split('.'); if (assemblyVersionNumbers.Length > 0) { assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]); assemblyVersion = String.Join(".", assemblyVersionNumbers); } } this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "version", assemblyVersion); } if (assemblyNameValues.ContainsKey("culture")) { string culture = assemblyNameValues["culture"]; this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "culture", (String.Empty == culture ? "neutral" : culture)); } if (assemblyNameValues.ContainsKey("publicKeyToken")) { this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "publicKeyToken", assemblyNameValues["publicKeyToken"]); } if (null != fileRow.ProcessorArchitecture && 0 < fileRow.ProcessorArchitecture.Length) { this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "processorArchitecture", fileRow.ProcessorArchitecture); } if (assemblyNameValues.ContainsKey("processorArchitecture")) { this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "processorArchitecture", assemblyNameValues["processorArchitecture"]); } } else if (FileAssemblyType.Win32Assembly == fileRow.AssemblyType) { FileRow fileManifestRow = fileRow; // would rather look this up through a data structure rather than // do an order n search through the list of files for every // instance of a win32 assembly. From what I can find, there // are no indexed data structures available at this point // in the code we're left with this expensive search. foreach (OutputRow manifestOutputRow in fileTable.OutputRows) { fileManifestRow = manifestOutputRow.Row as FileRow; if (fileManifestRow.File == fileRow.AssemblyManifest) { break; } } string type = null; string name = null; string version = null; string processorArchitecture = null; string publicKeyToken = null; // loading the dom is expensive we want more performant APIs than the DOM // Navigator is cheaper than dom. Perhaps there is a cheaper API still. string manifestSourcePath = null; try { manifestSourcePath = this.extension.FileResolutionHandler(fileManifestRow.Source, FileResolutionType.File); } catch (WixFileNotFoundException wfnfe) { this.OnMessage(WixErrors.BinderExtensionMissingFile(fileRow.SourceLineNumbers, ErrorLevel.Normal, wfnfe.Message)); continue; } try { XPathDocument doc = new XPathDocument(manifestSourcePath); XPathNavigator nav = doc.CreateNavigator(); nav.MoveToRoot(); // this assumes a particular schema for a win32 manifest and does not // provide error checking if the file does not conform to schema. // The fallback case here is that nothing is added to the MsiAssemblyName // table for a out of tollerence Win32 manifest. Perhaps warnings needed. if (nav.MoveToFirstChild()) { while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") { nav.MoveToNext(); } if (nav.MoveToFirstChild()) { while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity") { nav.MoveToNext(); } if (nav.MoveToAttribute("type", String.Empty)) { type = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("name", String.Empty)) { name = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("version", String.Empty)) { version = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("processorArchitecture", String.Empty)) { processorArchitecture = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("publicKeyToken", String.Empty)) { publicKeyToken = nav.Value; nav.MoveToParent(); } } } } catch (XmlException xe) { this.OnMessage(WixErrors.InvalidXml(SourceLineNumberCollection.FromFileName(manifestSourcePath), "manifest", xe.Message)); } OutputTable assemblyNameOutputTable = Common.EnsureOutputTable(output, this.tableDefinitions["MsiAssemblyName"]); if (null != name && 0 < name.Length) { this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "name", name); } if (null != version && 0 < version.Length) { this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "version", version); } if (null != type && 0 < type.Length) { this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "type", type); } if (null != processorArchitecture && 0 < processorArchitecture.Length) { this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "processorArchitecture", processorArchitecture); } if (null != publicKeyToken && 0 < publicKeyToken.Length) { this.SetMsiAssemblyName(assemblyNameOutputTable, fileRow, "publicKeyToken", publicKeyToken); } } } } }
/// <summary> /// Merges in any modules to the output database. /// </summary> /// <param name="databasePath">Path to database.</param> /// <param name="output">Output that specifies database and modules to merge.</param> /// <remarks>Expects that output's database has already been generated.</remarks> private void MergeModules(string databasePath, Output output) { Debug.Assert(OutputType.Product == output.Type); if (0 == output.Modules.Count) // no work to do { return; } IMsmMerge2 merge = null; bool commit = false; bool logOpen = false; bool databaseOpen = false; bool moduleOpen = false; try { bool foundError = false; MsmMerge msm = new MsmMerge(); merge = (IMsmMerge2)msm; merge.OpenLog(String.Concat(this.tempFiles.BasePath, Path.DirectorySeparatorChar, "merge.log")); logOpen = true; merge.OpenDatabase(databasePath); databaseOpen = true; // process all the merge rows foreach (MergeRow mergeRow in output.Modules) { string mergeModulePath = null; try { mergeModulePath = this.extension.FileResolutionHandler(mergeRow.SourceFile, FileResolutionType.Module); } catch (WixFileNotFoundException wfnfe) { this.OnMessage(WixErrors.BinderExtensionMissingFile(mergeRow.SourceLineNumbers, ErrorLevel.Normal, wfnfe.Message)); foundError = true; continue; } try { merge.OpenModule(mergeModulePath, mergeRow.Language); } catch (COMException ce) { if (-2147023273 == ce.ErrorCode) // 0x80070657 - ERROR_INSTALL_LANGUAGE_UNSUPPORTED { throw new WixUnknownMergeLanguageException(mergeRow.SourceLineNumbers, mergeRow.Id, mergeModulePath, mergeRow.Language, ce); } else { throw; } } moduleOpen = true; ConnectToFeature connection = output.ModulesToFeatures[mergeRow.Id]; if (null == connection) { throw new WixMergeModuleMissingFeatureException(mergeRow.SourceLineNumbers, mergeRow.Id); } string configData = mergeRow.ConfigurationData; if (null != configData) { ConfigurationCallback callback = new ConfigurationCallback(configData); merge.MergeEx(connection.PrimaryFeature, mergeRow.Directory, callback); } else { merge.Merge(connection.PrimaryFeature, mergeRow.Directory); } /* IMsmErrors errorCollection = null; merge.get_Errors(out errorCollection); long count = errorCollection.get_Count(); if (0 < count) { throw new WixMergeFailureException(null, this.tempFiles.BasePath, count, null); } */ foreach (string connectTo in connection.ConnectFeatures) { merge.Connect(connectTo); } // if the module has files and creating layout if (mergeRow.HasFiles && !this.suppressLayout) { string hashedMergeId = mergeRow.Id.GetHashCode().ToString("X4", CultureInfo.InvariantCulture.NumberFormat); // extract the module cabinet, then explode all of the files to a temp directory string moduleCabPath = String.Concat(this.tempFiles.BasePath, Path.DirectorySeparatorChar, hashedMergeId, ".module.cab"); merge.ExtractCAB(moduleCabPath); string mergeIdPath = String.Concat(this.tempFiles.BasePath, Path.DirectorySeparatorChar, "MergeId.", hashedMergeId); Directory.CreateDirectory(mergeIdPath); WixExtractCab extCab = null; try { extCab = new WixExtractCab(); extCab.Extract(moduleCabPath, mergeIdPath); } catch (WixCabExtractionException wce) { COMException comException = wce.InnerException as COMException; foundError = true; if (null != comException && 0x80070002 == unchecked((uint)comException.ErrorCode)) { extCab = null; // Cab doesn't exist, so drop the object. this.OnMessage(WixErrors.CabFileDoesNotExist(moduleCabPath, mergeModulePath, mergeIdPath)); } else { this.OnMessage(WixErrors.CabExtractionFailed(moduleCabPath, mergeModulePath, mergeIdPath)); } } finally { if (null != extCab) { try { extCab.Close(); } catch (WixCabExtractionException) { this.OnMessage(WixErrors.CabClosureFailed(moduleCabPath)); } } } } moduleOpen = false; merge.CloseModule(); } commit = !foundError; // if all seems to have progressed cleanly, feel free to commit the changes to the database } finally { if (moduleOpen) { merge.CloseModule(); } if (databaseOpen) { merge.CloseDatabase(commit); } if (logOpen) { merge.CloseLog(); } } // create a Hashtable of all the suppressed sequence types Hashtable suppressedTableNames = new Hashtable(); if (output.SuppressAdminSequence) { suppressedTableNames[Action.SequenceTypeToString(SequenceType.adminExecute)] = null; suppressedTableNames[Action.SequenceTypeToString(SequenceType.adminUI)] = null; } if (output.SuppressAdvertiseSequence) { suppressedTableNames[Action.SequenceTypeToString(SequenceType.advertiseExecute)] = null; } if (output.SuppressUISequence) { suppressedTableNames[Action.SequenceTypeToString(SequenceType.adminUI)] = null; suppressedTableNames[Action.SequenceTypeToString(SequenceType.installUI)] = null; } using (Database db = new Database(databasePath, OpenDatabase.Direct)) { OutputTable suppressActionOutputTable = output.OutputTables["SuppressAction"]; // suppress individual actions if (null != suppressActionOutputTable) { foreach (OutputRow outputRow in suppressActionOutputTable.OutputRows) { if (db.TableExists((string)outputRow.Row[0])) { Row row = outputRow.Row; string query = String.Format("SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); using (View view = db.OpenExecuteView(query)) { Record record; if (view.Fetch(out record)) { this.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); view.Modify(ModifyView.Delete, record); record.Close(); } } } } } // query for merge module actions in suppressed sequences and drop them foreach (string tableName in suppressedTableNames.Keys) { if (!db.TableExists(tableName)) { continue; } using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) { Record resultRecord; while (view.Fetch(out resultRecord)) { this.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); resultRecord.Close(); } } // drop suppressed sequences using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) { } // delete the validation rows using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) { Record record = new Record(1); record.SetString(1, tableName); view.Execute(record); } } // now update the Attributes column for the files from the Merge Modules using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) { foreach (FileMediaInformation fmi in output.FileMediaInformationCollection) { if (!fmi.IsInModule) { continue; } Record record = new Record(1); record.SetString(1, fmi.File); view.Execute(record); Record recordUpdate; view.Fetch(out recordUpdate); if (null == recordUpdate) { throw new WixMergeFailureException(null, this.tempFiles.BasePath, 1, null); } recordUpdate.SetInteger(1, fmi.Sequence); // update the file attributes to match the compression specified // on the Merge element or on the Package element int attributes = 0; // get the current value if its not null if (!recordUpdate.IsNull(2)) { attributes = recordUpdate.GetInteger(2); } if (FileCompressionValue.Yes == fmi.FileCompression) { attributes |= MsiInterop.MsidbFileAttributesCompressed; } else if (FileCompressionValue.No == fmi.FileCompression) { attributes |= MsiInterop.MsidbFileAttributesNoncompressed; } else // not specified { Debug.Assert(FileCompressionValue.NotSpecified == fmi.FileCompression); // clear any compression bits attributes &= ~MsiInterop.MsidbFileAttributesCompressed; attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; } recordUpdate.SetInteger(2, attributes); view.Modify(ModifyView.Update, recordUpdate); } } db.Commit(); } }
/// <summary> /// Creates the MSI/MSM/PCP database. /// </summary> /// <param name="output">Output to create database for.</param> /// <param name="databaseFile">The database file to create.</param> /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param> /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param> internal void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) { // add the _Validation rows if (!this.suppressAddingValidationRows) { Table validationTable = output.EnsureTable(this.core.TableDefinitions["_Validation"]); foreach (Table table in output.Tables) { if (!table.Definition.IsUnreal) { // add the validation rows for this table table.Definition.AddValidationRows(validationTable); } } } // set the base directory string baseDirectory = this.TempFilesLocation; if (useSubdirectory) { string filename = Path.GetFileNameWithoutExtension(databaseFile); baseDirectory = Path.Combine(baseDirectory, filename); // make sure the directory exists Directory.CreateDirectory(baseDirectory); } try { OpenDatabase type = OpenDatabase.CreateDirect; // set special flag for patch files if (OutputType.Patch == output.Type) { type |= OpenDatabase.OpenPatchFile; } // try to create the database using (Database db = new Database(databaseFile, type)) { // localize the codepage if a value was specified by the localizer if (null != this.Localizer && -1 != this.Localizer.Codepage) { output.Codepage = this.Localizer.Codepage; } // if we're not using the default codepage, import a new one into our // database before we add any tables (or the tables would be added // with the wrong codepage) if (0 != output.Codepage) { this.SetDatabaseCodepage(db, output); } // insert substorages (like transforms inside a patch) if (0 < output.SubStorages.Count) { using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) { foreach (SubStorage subStorage in output.SubStorages) { string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst")); // bind the transform if (this.BindTransform(subStorage.Data, transformFile)) { // add the storage using (Record record = new Record(2)) { record.SetString(1, subStorage.Name); record.SetStream(2, transformFile); storagesView.Modify(ModifyView.Assign, record); } } } } // some empty transforms may have been excluded // we need to remove these from the final patch summary information if (OutputType.Patch == output.Type && this.AllowEmptyTransforms) { Table patchSummaryInfo = output.EnsureTable(this.core.TableDefinitions["_SummaryInformation"]); for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--) { Row row = patchSummaryInfo.Rows[i]; if ((int)SummaryInformation.Patch.ProductCodes == (int)row[0]) { if (nonEmptyProductCodes.Count > 0) { string[] productCodes = new string[nonEmptyProductCodes.Count]; nonEmptyProductCodes.CopyTo(productCodes, 0); row[1] = String.Join(";", productCodes); } else { row[1] = Binder.NullString; } } else if ((int)SummaryInformation.Patch.TransformNames == (int)row[0]) { if (nonEmptyTransformNames.Count > 0) { string[] transformNames = new string[nonEmptyTransformNames.Count]; nonEmptyTransformNames.CopyTo(transformNames, 0); row[1] = String.Join(";", transformNames); } else { row[1] = Binder.NullString; } } } } } foreach (Table table in output.Tables) { Table importTable = table; bool hasBinaryColumn = false; // skip all unreal tables other than _Streams if (table.Definition.IsUnreal && "_Streams" != table.Name) { continue; } // Do not put the _Validation table in patches, it is not needed if (OutputType.Patch == output.Type && "_Validation" == table.Name) { continue; } // The only way to import binary data is to copy it to a local subdirectory first. // To avoid this extra copying and perf hit, import an empty table with the same // definition and later import the binary data from source using records. foreach (ColumnDefinition columnDefinition in table.Definition.Columns) { if (ColumnType.Object == columnDefinition.Type) { importTable = new Table(table.Section, table.Definition); hasBinaryColumn = true; break; } } // create the table via IDT import if ("_Streams" != importTable.Name) { try { db.ImportTable(output.Codepage, this.core, importTable, baseDirectory, keepAddedColumns); } catch (WixInvalidIdtException) { // If ValidateRows finds anything it doesn't like, it throws importTable.ValidateRows(); // Otherwise we rethrow the InvalidIdt throw; } } // insert the rows via SQL query if this table contains object fields if (hasBinaryColumn) { StringBuilder query = new StringBuilder("SELECT "); // build the query for the view bool firstColumn = true; foreach (ColumnDefinition columnDefinition in table.Definition.Columns) { if (!firstColumn) { query.Append(","); } query.AppendFormat(" `{0}`", columnDefinition.Name); firstColumn = false; } query.AppendFormat(" FROM `{0}`", table.Name); using (View tableView = db.OpenExecuteView(query.ToString())) { // import each row containing a stream foreach (Row row in table.Rows) { using (Record record = new Record(table.Definition.Columns.Count)) { StringBuilder streamName = new StringBuilder(); bool needStream = false; // the _Streams table doesn't prepend the table name (or a period) if ("_Streams" != table.Name) { streamName.Append(table.Name); } for (int i = 0; i < table.Definition.Columns.Count; i++) { ColumnDefinition columnDefinition = table.Definition.Columns[i]; switch (columnDefinition.Type) { case ColumnType.Localized: case ColumnType.Preserved: case ColumnType.String: if (columnDefinition.IsPrimaryKey) { if (0 < streamName.Length) { streamName.Append("."); } streamName.Append((string)row[i]); } record.SetString(i + 1, (string)row[i]); break; case ColumnType.Number: record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture)); break; case ColumnType.Object: if (null != row[i]) { needStream = true; try { record.SetStream(i + 1, (string)row[i]); } catch (Win32Exception e) { if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME { throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i])); } else { throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); } } } break; } } // stream names are created by concatenating the name of the table with the values // of the primary key (delimited by periods) // check for a stream name that is more than 62 characters long (the maximum allowed length) if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length) { this.core.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); } else // add the row to the database { tableView.Modify(ModifyView.Assign, record); } } } } // Remove rows from the _Streams table for wixpdbs. if ("_Streams" == table.Name) { table.Rows.Clear(); } } } // we're good, commit the changes to the new MSI db.Commit(); } } catch (IOException) { // TODO: this error message doesn't seem specific enough throw new WixFileNotFoundException(SourceLineNumberCollection.FromFileName(databaseFile), databaseFile); } }
/// <summary> /// Extracts cab files from the specified MSIDB and puts them in the specified outputdir. /// </summary> /// <param name="msidb"></param> /// <param name="outputDir"></param> /// <returns></returns> private static List<CabInfo> CabsFromMsiToDisk(Path msi, Database msidb, string outputDir) { const string query = "SELECT * FROM `Media`"; var localCabFiles = new List<CabInfo>(); using (View view = msidb.OpenExecuteView(query)) { Record record; while (view.Fetch(out record)) { const int MsiInterop_Media_Cabinet = 4; string cabSourceName = record[MsiInterop_Media_Cabinet]; if (string.IsNullOrEmpty(cabSourceName)) { Debug.Print("Empty Cabinet value in Media table. This happens, but it's rare and it's weird!");//Debug.Fail("Couldn't find media CAB file inside the MSI (bad media table?)."); continue; } if (!string.IsNullOrEmpty(cabSourceName)) { bool extract = false; if (cabSourceName.StartsWith("#")) { extract = true; cabSourceName = cabSourceName.Substring(1); } Path localCabFile = Path.Combine(outputDir, cabSourceName); if (extract) { // extract cabinet, then explode all of the files to a temp directory ExtractCabFromPackage(localCabFile, cabSourceName, msidb); } else { Path originalCabFile = Path.Combine(msi.Parent, cabSourceName); FileSystem.Copy(originalCabFile, localCabFile); } /* http://code.google.com/p/lessmsi/issues/detail?id=1 * apparently in some cases a file spans multiple CABs (VBRuntime.msi) so due to that we have get all CAB files out of the MSI and then begin extraction. Then after we extract everything out of all CAbs we need to release the CAB extractors and delete temp files. * Thanks to Christopher Hamburg for explaining this! */ var c = new CabInfo(localCabFile.PathString, cabSourceName); localCabFiles.Add(c); } } } return localCabFiles; }
/// <summary> /// Write the Cab to disk. /// </summary> /// <param name="filePath">Specifies the path to the file to contain the stream.</param> /// <param name="cabName">Specifies the name of the file in the stream.</param> public static void ExtractCabFromPackage(Path filePath, string cabName, Database inputDatabase) { using (View view = inputDatabase.OpenExecuteView(String.Concat("SELECT * FROM `_Streams` WHERE `Name` = '", cabName, "'"))) { Record record; if (view.Fetch(out record)) { using (var writer = new System.IO.BinaryWriter(FileSystem.CreateFile(filePath))) { var buf = new byte[1024*1024]; int count; do { const int MsiInterop_Storages_Data = 2; //From wiX:Index to column name Data into Record for row in Msi Table Storages count = record.GetStream(MsiInterop_Storages_Data, buf, buf.Length); if (count > 0) writer.Write(buf, 0, count); } while (count > 0); } } } }
/// <summary> /// Shows the table in the list on the view table tab. /// </summary> /// <param name="msidb">The msi database.</param> /// <param name="tableName">The name of the table.</param> private void UpdateMSiTableGrid(Database msidb, string tableName) { if (msidb == null || string.IsNullOrEmpty(tableName)) return; Status(string.Concat("Processing Table \'", tableName, "\'.")); using (new DisposableCursor(View)) { // clear the columns no matter what happens (in the event the table doesn't exist we don't want to show anything). View.ClearTableViewGridColumns(); try { // NOTE: Deliberately not calling msidb.TableExists here as some System tables could not be read due to using it. string query = string.Concat("SELECT * FROM `", tableName, "`"); using (var view = new ViewWrapper(msidb.OpenExecuteView(query))) { foreach (ColumnInfo col in view.Columns) { View.AddTableViewGridColumn(string.Concat(col.Name, " (", col.TypeID, ")")); } View.SetTableViewGridDataSource(view.Records); } Status("Idle"); } catch (Exception eUnexpected) { Error(string.Concat("Cannot view table:", eUnexpected.Message), eUnexpected); } } }
/// <summary> /// Unbind an MSI database file. /// </summary> /// <param name="databaseFile">The database file.</param> /// <param name="database">The opened database.</param> /// <param name="outputType">The type of output to create.</param> /// <param name="exportBasePath">The path where files should be exported.</param> /// <param name="skipSummaryInfo">Option to skip unbinding the _SummaryInformation table.</param> /// <returns>The output representing the database.</returns> private Output UnbindDatabase(string databaseFile, Database database, OutputType outputType, string exportBasePath, bool skipSummaryInfo) { string modularizationGuid = null; Output output = new Output(SourceLineNumberCollection.FromFileName(databaseFile)); View validationView = null; // set the output type output.Type = outputType; // get the codepage database.Export("_ForceCodepage", this.TempFilesLocation, "_ForceCodepage.idt"); using (StreamReader sr = File.OpenText(Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"))) { string line; while (null != (line = sr.ReadLine())) { string[] data = line.Split('\t'); if (2 == data.Length) { output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); } } } // get the summary information table if it exists; it won't if unbinding a transform if (!skipSummaryInfo) { using (SummaryInformation summaryInformation = new SummaryInformation(database)) { Table table = new Table(null, this.tableDefinitions["_SummaryInformation"]); for (int i = 1; 19 >= i; i++) { string value = summaryInformation.GetProperty(i); if (0 < value.Length) { Row row = table.CreateRow(output.SourceLineNumbers); row[0] = i; row[1] = value; } } output.Tables.Add(table); } } try { // open a view on the validation table if it exists if (database.TableExists("_Validation")) { validationView = database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); } // get the normal tables using (View tablesView = database.OpenExecuteView("SELECT * FROM _Tables")) { while (true) { using (Record tableRecord = tablesView.Fetch()) { if (null == tableRecord) { break; } string tableName = tableRecord.GetString(1); using (View tableView = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) { TableDefinition tableDefinition = new TableDefinition(tableName, false, false); Hashtable tablePrimaryKeys = new Hashtable(); using (Record columnNameRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFONAMES), columnTypeRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFOTYPES)) { int columnCount = columnNameRecord.GetFieldCount(); // index the primary keys using (Record primaryKeysRecord = database.PrimaryKeys(tableName)) { int primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); for (int i = 1; i <= primaryKeysFieldCount; i++) { tablePrimaryKeys[primaryKeysRecord.GetString(i)] = null; } } for (int i = 1; i <= columnCount; i++) { string columnName = columnNameRecord.GetString(i); string idtType = columnTypeRecord.GetString(i); ColumnType columnType; int length; bool nullable; ColumnCategory columnCategory = ColumnCategory.Unknown; ColumnModularizeType columnModularizeType = ColumnModularizeType.None; bool primary = tablePrimaryKeys.Contains(columnName); bool minValueSet = false; int minValue = -1; bool maxValueSet = false; int maxValue = -1; string keyTable = null; bool keyColumnSet = false; int keyColumn = -1; string category = null; string set = null; string description = null; // get the column type, length, and whether its nullable switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) { case 'i': columnType = ColumnType.Number; break; case 'l': columnType = ColumnType.Localized; break; case 's': columnType = ColumnType.String; break; case 'v': columnType = ColumnType.Object; break; default: // TODO: error columnType = ColumnType.Unknown; break; } length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); nullable = Char.IsUpper(idtType[0]); // try to get validation information if (null != validationView) { using (Record validationRecord = new Record(2)) { validationRecord.SetString(1, tableName); validationRecord.SetString(2, columnName); validationView.Execute(validationRecord); } using (Record validationRecord = validationView.Fetch()) { if (null != validationRecord) { string validationNullable = validationRecord.GetString(3); minValueSet = !validationRecord.IsNull(4); minValue = (minValueSet ? validationRecord.GetInteger(4) : -1); maxValueSet = !validationRecord.IsNull(5); maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1); keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null); keyColumnSet = !validationRecord.IsNull(7); keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1); category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null); set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null); description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null); // check the validation nullable value against the column definition if (null == validationNullable) { // TODO: warn for illegal validation nullable column } else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) { // TODO: warn for mismatch between column definition and validation nullable } // convert category to ColumnCategory if (null != category) { try { columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); } catch (ArgumentException) { columnCategory = ColumnCategory.Unknown; } } } else { // TODO: warn about no validation information } } } // guess the modularization type if ("Icon" == keyTable && 1 == keyColumn) { columnModularizeType = ColumnModularizeType.Icon; } else if ("Condition" == columnName) { columnModularizeType = ColumnModularizeType.Condition; } else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory) { columnModularizeType = ColumnModularizeType.Property; } else if (ColumnCategory.Identifier == columnCategory) { columnModularizeType = ColumnModularizeType.Column; } tableDefinition.Columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true)); } } // use our table definitions if core properties are the same; this allows us to take advantage // of wix concepts like localizable columns which current code assumes if (this.tableDefinitions.Contains(tableName) && 0 == tableDefinition.CompareTo(this.tableDefinitions[tableName])) { tableDefinition = this.tableDefinitions[tableName]; } Table table = new Table(null, tableDefinition); while (true) { using (Record rowRecord = tableView.Fetch()) { if (null == rowRecord) { break; } int recordCount = rowRecord.GetFieldCount(); Row row = table.CreateRow(output.SourceLineNumbers); for (int i = 0; recordCount > i && row.Fields.Length > i; i++) { if (rowRecord.IsNull(i + 1)) { if (!row.Fields[i].Column.IsNullable) { // TODO: display an error for a null value in a non-nullable field OR // display a warning and put an empty string in the value to let the compiler handle it // (the second option is risky because the later code may make certain assumptions about // the contents of a row value) } } else { switch (row.Fields[i].Column.Type) { case ColumnType.Number: bool success = false; int intValue = rowRecord.GetInteger(i + 1); if (row.Fields[i].Column.IsLocalizable) { success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)); } else { success = row.BestEffortSetField(i, intValue); } if (!success) { this.OnMessage(WixWarnings.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); } break; case ColumnType.Object: string sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; if (null != exportBasePath) { string relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); sourceFile = Path.Combine(exportBasePath, relativeSourceFile); // ensure the parent directory exists System.IO.Directory.CreateDirectory(Path.Combine(exportBasePath, tableName)); using (FileStream fs = System.IO.File.Create(sourceFile)) { int bytesRead; byte[] buffer = new byte[512]; while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } } row[i] = sourceFile; break; default: string value = rowRecord.GetString(i + 1); switch (row.Fields[i].Column.Category) { case ColumnCategory.Guid: value = value.ToUpper(CultureInfo.InvariantCulture); break; } // de-modularize if (!this.suppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) { Regex modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); if (null == modularizationGuid) { Match match = modularization.Match(value); if (match.Success) { modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); } } value = modularization.Replace(value, String.Empty); } // escape "$(" for the preprocessor value = value.Replace("$(", "$$("); // escape things that look like wix variables MatchCollection matches = Common.WixVariableRegex.Matches(value); for (int j = matches.Count - 1; 0 <= j; j--) { value = value.Insert(matches[j].Index, "!"); } row[i] = value; break; } } } } } output.Tables.Add(table); } } } } } finally { if (null != validationView) { validationView.Close(); } } // set the modularization guid as the PackageCode if (null != modularizationGuid) { Table table = output.Tables["_SummaryInformation"]; foreach (Row row in table.Rows) { if (9 == (int)row[0]) // PID_REVNUMBER { row[1] = modularizationGuid; } } } if (this.isAdminImage) { GenerateWixFileTable(databaseFile, output); GenerateSectionIds(output); } return output; }
/// <summary> /// Extracts cab files from the specified MSIDB and puts them in the specified outputdir. /// </summary> /// <param name="msidb"></param> /// <param name="outputDir"></param> /// <returns></returns> private static List<CabInfo> CabsFromMsiToDisk(Database msidb, DirectoryInfo outputDir) { const string query = "SELECT * FROM `Media`"; var localCabFiles = new List<CabInfo>(); using (View view = msidb.OpenExecuteView(query)) { Record record; while (view.Fetch(out record)) { const int MsiInterop_Media_Cabinet = 4; string cabSourceName = record[MsiInterop_Media_Cabinet]; if (string.IsNullOrEmpty(cabSourceName)) throw new IOException("Couldn't find media CAB file inside the MSI (bad media table?)."); if (!string.IsNullOrEmpty(cabSourceName)) { if (cabSourceName.StartsWith("#")) { cabSourceName = cabSourceName.Substring(1); // extract cabinet, then explode all of the files to a temp directory string localCabFile = Path.Combine(outputDir.FullName, cabSourceName); ExtractCabFromPackage(localCabFile, cabSourceName, msidb); /* http://code.google.com/p/lessmsi/issues/detail?id=1 * apparently in some cases a file spans multiple CABs (VBRuntime.msi) so due to that we have get all CAB files out of the MSI and then begin extraction. Then after we extract everything out of all CAbs we need to release the CAB extractors and delete temp files. * Thanks to Christopher Hamburg for explaining this! */ var c = new CabInfo(localCabFile, cabSourceName); localCabFiles.Add(c); } } } } return localCabFiles; }
/// <summary> /// Validate a database. /// </summary> /// <param name="databaseFile">The database to validate.</param> /// <returns>true if validation succeeded; false otherwise.</returns> public bool Validate(string databaseFile) { Dictionary<string, string> indexedICEs = new Dictionary<string, string>(); Dictionary<string, string> indexedSuppressedICEs = new Dictionary<string, string>(); int previousUILevel = (int)InstallUILevels.Basic; IntPtr previousHwnd = IntPtr.Zero; InstallUIHandler previousUIHandler = null; if (null == databaseFile) { throw new ArgumentNullException("databaseFile"); } // initialize the validator extension this.extension.DatabaseFile = databaseFile; this.extension.Output = this.output; this.extension.InitializeValidator(); // if we don't have the temporary files object yet, get one if (null == this.tempFiles) { this.tempFiles = new TempFileCollection(); } Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there // index the ICEs if (null != this.ices) { foreach (string ice in this.ices) { indexedICEs[ice] = null; } } // index the suppressed ICEs if (null != this.suppressedICEs) { foreach (string suppressedICE in this.suppressedICEs) { indexedSuppressedICEs[suppressedICE] = null; } } // copy the database to a temporary location so it can be manipulated string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(databaseFile)); File.Copy(databaseFile, tempDatabaseFile); // remove the read-only property from the temporary database FileAttributes attributes = File.GetAttributes(tempDatabaseFile); File.SetAttributes(tempDatabaseFile, attributes & ~FileAttributes.ReadOnly); Mutex mutex = new Mutex(false, "WixValidator"); try { if (!mutex.WaitOne(0)) { this.OnMessage(WixVerboses.ValidationSerialized()); mutex.WaitOne(); } using (Database database = new Database(tempDatabaseFile, OpenDatabase.Direct)) { bool propertyTableExists = database.TableExists("Property"); string productCode = null; // remove the product code from the database before opening a session to prevent opening an installed product if (propertyTableExists) { using (View view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'")) { using (Record record = view.Fetch()) { if (null != record) { productCode = record.GetString(1); using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) { } } } } } // merge in the cube databases foreach (string cubeFile in this.cubeFiles) { try { using (Database cubeDatabase = new Database(cubeFile, OpenDatabase.ReadOnly)) { try { database.Merge(cubeDatabase, "MergeConflicts"); } catch { // ignore merge errors since they are expected in the _Validation table } } } catch (Win32Exception e) { if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED { throw new WixException(WixErrors.CubeFileNotFound(cubeFile)); } throw; } } // commit the database before proceeding to ensure the streams don't get confused database.Commit(); // the property table may have been added to the database // from a cub database without the proper validation rows if (!propertyTableExists) { using (View view = database.OpenExecuteView("DROP table `Property`")) { } } // get all the action names for ICEs which have not been suppressed List<string> actions = new List<string>(); using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`")) { while (true) { using (Record record = view.Fetch()) { if (null == record) { break; } string action = record.GetString(1); if (!indexedSuppressedICEs.ContainsKey(action)) { actions.Add(action); } } } } if (0 != indexedICEs.Count) { // Walk backwards and remove those that arent in the list for (int i = actions.Count - 1; 0 <= i; i--) { if (!indexedICEs.ContainsKey(actions[i])) { actions.RemoveAt(i); } } } // disable the internal UI handler and set an external UI handler previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd); previousUIHandler = Installer.SetExternalUI(this.validationUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero); // create a session for running the ICEs this.validationSessionComplete = false; using (Session session = new Session(database)) { // add the product code back into the database if (null != productCode) { // some CUBs erroneously have a ProductCode property, so delete it if we just picked one up using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) { } using (View view = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{0}')", productCode))) { } } foreach (string action in actions) { this.actionName = action; try { session.DoAction(action); } catch (Win32Exception e) { if (!this.encounteredError) { throw e; } else { this.encounteredError = false; } } this.actionName = null; } // Mark the validation session complete so we ignore any messages that MSI may fire // during session clean-up. this.validationSessionComplete = true; } } } catch (Win32Exception e) { // avoid displaying errors twice since one may have already occurred in the UI handler if (!this.encounteredError) { if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED { // databaseFile is not passed since during light // this would be the temporary copy and there would be // no final output since the error occured; during smoke // they should know the path passed into smoke this.OnMessage(WixErrors.ValidationFailedToOpenDatabase()); } else if (0x64D == e.NativeErrorCode) { this.OnMessage(WixErrors.ValidationFailedDueToLowMsiEngine()); } else if (0x654 == e.NativeErrorCode) { this.OnMessage(WixErrors.ValidationFailedDueToInvalidPackage()); } else if (0x658 == e.NativeErrorCode) { this.OnMessage(WixErrors.ValidationFailedDueToMultilanguageMergeModule()); } else if (0x659 == e.NativeErrorCode) { this.OnMessage(WixWarnings.ValidationFailedDueToSystemPolicy()); } else { string msgTemp = e.Message; if (null != this.actionName) { msgTemp = String.Concat("Action - '", this.actionName, "' ", e.Message); } this.OnMessage(WixErrors.Win32Exception(e.NativeErrorCode, msgTemp)); } } } finally { Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero); Installer.SetInternalUI(previousUILevel, ref previousHwnd); this.validationSessionComplete = false; // no validation session at this point, so reset the completion flag. mutex.ReleaseMutex(); this.cubeFiles.Clear(); this.extension.FinalizeValidator(); } return !this.encounteredError; }
/// <summary> /// Query an MSI table for a single value /// </summary> /// <param name="msi">The path to an MSI</param> /// <param name="sql">An MSI query</param> /// <returns>The results as a string or null if no results are returned</returns> /// <remarks> /// Returns the value of the first field in the first record /// </remarks> public static string Query(string msi, string query) { string result = null; using (Database database = new Database(msi, OpenDatabase.ReadOnly)) { using (View view = database.OpenExecuteView(query)) { using (Microsoft.Tools.WindowsInstallerXml.Msi.Record record = view.Fetch()) { if (null != record) { result = Convert.ToString(record.GetString(1)); } } } } return result; }
/// <summary> /// Gets the upgrade code (Product GUID) for the MSI file /// </summary> /// <param name="msiFileString"></param> /// <returns></returns> private bool GetMSIProperties(string msiFileString) { bool isDone = false; if (msiFileString == null) { Console.WriteLine("Unable to extract MSI"); return isDone; } using (Database msidb = new Database(msiFileString, OpenDatabase.ReadOnly)) { const string tableName = "Property"; string query = string.Concat("SELECT * FROM `", tableName, "`"); View propView = msidb.OpenExecuteView(query); Record columns = propView.Fetch(); while (columns != null) { InstallerProps.Add(columns[1], columns[2]); columns = propView.Fetch(); } isDone = true; } return isDone; }