/// <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(); } }
/// <summary> /// Opens the previous package (.msi or .exe) and reads the interesting information from it. /// </summary> /// <param name="filePath">Path to the package.</param> private void ReadPreviousPackage(string filePath) { using (Database db = new Database(filePath, OpenDatabase.ReadOnly)) { using (View view = db.OpenView("SELECT `Value` FROM `Property` WHERE `Property`=?")) { string propertyValue; // get the UpgradeCode propertyValue = this.FetchPropertyValue(view, "UpgradeCode"); if (propertyValue != null) { this.previousUpgradeCode = new Guid(propertyValue); } // get the Version propertyValue = this.FetchPropertyValue(view, "ProductVersion"); if (propertyValue != null) { this.previousVersion = new Version(propertyValue); } // get the Update URL propertyValue = this.FetchPropertyValue(view, "ARPURLUPDATEINFO"); if (propertyValue != null) { this.previousUri = new Uri(propertyValue); } } } }
/// <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> /// 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> /// Extract the cabinets from a database. /// </summary> /// <param name="output">The output to use when finding cabinets.</param> /// <param name="database">The database containing the cabinets.</param> /// <param name="databaseFile">The location of the database file.</param> /// <param name="exportBasePath">The path where the files should be exported.</param> private void ExtractCabinets(Output output, Database database, string databaseFile, string exportBasePath) { string databaseBasePath = Path.GetDirectoryName(databaseFile); StringCollection cabinetFiles = new StringCollection(); SortedList embeddedCabinets = new SortedList(); // index all of the cabinet files if (OutputType.Module == output.Type) { embeddedCabinets.Add(0, "MergeModule.CABinet"); } else if (null != output.Tables["Media"]) { foreach (MediaRow mediaRow in output.Tables["Media"].Rows) { if (null != mediaRow.Cabinet) { if (OutputType.Product == output.Type || (OutputType.Transform == output.Type && RowOperation.Add == mediaRow.Operation)) { if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) { embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1)); } else { cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet)); } } } } } // extract the embedded cabinet files from the database if (0 < embeddedCabinets.Count) { using (View streamsView = database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) { foreach (int diskId in embeddedCabinets.Keys) { using(Record record = new Record(1)) { record.SetString(1, (string)embeddedCabinets[diskId]); streamsView.Execute(record); } using (Record record = streamsView.Fetch()) { if (null != record) { // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not case-sensitive, // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work string cabinetFile = Path.Combine(this.TempFilesLocation, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); // ensure the parent directory exists System.IO.Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); using (FileStream fs = System.IO.File.Create(cabinetFile)) { int bytesRead; byte[] buffer = new byte[512]; while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } cabinetFiles.Add(cabinetFile); } else { // TODO: warning about missing embedded cabinet } } } } } // extract the cabinet files if (0 < cabinetFiles.Count) { string fileDirectory = Path.Combine(exportBasePath, "File"); // delete the directory and its files to prevent cab extraction due to an existing file if (Directory.Exists(fileDirectory)) { Directory.Delete(fileDirectory, true); } // ensure the directory exists or extraction will fail Directory.CreateDirectory(fileDirectory); foreach (string cabinetFile in cabinetFiles) { using (WixExtractCab extractCab = new WixExtractCab()) { try { extractCab.Extract(cabinetFile, fileDirectory); } catch (FileNotFoundException) { throw new WixException(WixErrors.FileNotFound(SourceLineNumberCollection.FromFileName(databaseFile), cabinetFile)); } } } } }
/// <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> /// 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> /// Opens the previous package (.msi or .exe) and reads the interesting information from it. /// </summary> /// <param name="filePath">Path to the package.</param> /// <param name="previousUpgradeCode">Upgrade code of the package.</param> /// <param name="previousVersion">Version of the package.</param> /// <param name="previousUri">Update URL of the package.</param> private void ReadPreviousPackage(string filePath, out Guid previousUpgradeCode, out Version previousVersion, out Uri previousUri) { // assume nothing about the previous package previousUpgradeCode = Guid.Empty; previousVersion = null; previousUri = null; string tempFileName = null; Database db = null; View view = null; try { string msiPath = filePath; // assume the file path is the path to the MSI // if the extension on the file path is ".exe" try to extract the MSI out of it if (String.Compare(Path.GetExtension(filePath), ".exe", true) == 0) { tempFileName = Path.GetTempFileName(); Process process = new Process(); process.StartInfo.FileName = filePath; process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.Arguments = String.Concat("-out ", tempFileName); process.Start(); process.WaitForExit(); if (process.ExitCode != 0) { throw new ApplicationException(String.Concat("Failed to extract MSI from ", process.StartInfo.FileName)); } msiPath = tempFileName; // the MSI is now at the temp filename location } db = new Database(msiPath, OpenDatabase.ReadOnly); view = db.OpenView("SELECT `Value` FROM `Property` WHERE `Property`=?"); string propertyValue; // get the UpgradeCode propertyValue = this.FetchPropertyValue(view, "UpgradeCode"); if (propertyValue != null) { previousUpgradeCode = new Guid(propertyValue); } // get the Version propertyValue = this.FetchPropertyValue(view, "ProductVersion"); if (propertyValue != null) { previousVersion = new Version(propertyValue); } // get the Update URL propertyValue = this.FetchPropertyValue(view, "ARPURLUPDATEINFO"); if (propertyValue != null) { previousUri = new Uri(propertyValue); } } finally { if (view != null) { view.Close(); } if (db != null) { db.Close(); } if (tempFileName != null) { File.Delete(tempFileName); } } }