/// <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> /// 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> /// Processes the Dialog table. /// </summary> /// <param name="parentWriter">XmlWriter where the Intermediate should persist itself as XML.</param> private void ProcessDialogTable(XmlWriter parentWriter) { const string tableName = "Dialog"; if (!this.inputDatabase.TableExists(tableName)) { return; } using (View view = this.inputDatabase.OpenExecuteView(String.Concat("SELECT * FROM `", tableName, "`"))) { Record record; while (view.Fetch(out record)) { XmlTextWriter writer = this.InitializeXmlTextWriter(Path.GetFullPath(Path.Combine(this.outputFolder, String.Concat("SkuLibrary\\UI\\Dialogs\\", record[(int)MsiInterop.Dialog.Dialog], ".wxs")))); if (this.generateFragments) { // initalize the fragment writer.WriteStartElement("Fragment"); this.core.WriteAttributeString(writer, "Id", record[(int)MsiInterop.Dialog.Dialog]); // initalize the UI element writer.WriteStartElement("UI"); // create reference to fragment parentWriter.WriteStartElement("FragmentRef"); this.core.WriteAttributeString(parentWriter, "Id", record[(int)MsiInterop.Dialog.Dialog]); parentWriter.WriteEndElement(); } // TODO: test this for blowing up when null, error violation of SDK constraint "This must be a non-negative number" int bits = Convert.ToInt32(record[(int)MsiInterop.Dialog.Attributes]) ^ MsiInterop.DialogAttributesInvert; // nullable field string firstControl = record[(int)MsiInterop.Dialog.ControlFirst]; string defaultControl = record[(int)MsiInterop.Dialog.ControlDefault]; string cancelControl = record[(int)MsiInterop.Dialog.ControlCancel]; string str; writer.WriteStartElement(tableName); this.core.WriteAttributeString(writer, "Id", record[(int)MsiInterop.Dialog.Dialog]); if (50 != Convert.ToInt32(record[(int)MsiInterop.Dialog.HCentering])) // non-nullable field { this.core.WriteAttributeString(writer, "X", record[(int)MsiInterop.Dialog.HCentering]); } if (50 != Convert.ToInt32(record[(int)MsiInterop.Dialog.VCentering])) // non-nullable field { this.core.WriteAttributeString(writer, "Y", record[(int)MsiInterop.Dialog.VCentering]); } this.core.WriteAttributeString(writer, "Width", record[(int)MsiInterop.Dialog.Width]); this.core.WriteAttributeString(writer, "Height", record[(int)MsiInterop.Dialog.Height]); str = record[(int)MsiInterop.Dialog.Title]; this.core.WriteAttributeString(writer, "Title", str); if (0 < (bits & MsiInterop.MsidbDialogAttributesModal)) { bits = bits & ~MsiInterop.MsidbDialogAttributesMinimize; } if (0 < (bits & MsiInterop.MsidbDialogAttributesError)) { this.core.WriteAttributeString(writer, "ErrorDialog", "yes"); // can we discover this ourselves? } for (int index = 0; index <= MsiInterop.DialogAttributes.Length; index++) { if (0 < (bits & 1)) { string name = MsiInterop.DialogAttributes[index]; if (null == name || 0 == name.Length) { // TODO: throw an exception - Fail "Unknown attribute at bit position " & index } this.core.WriteAttributeString(writer, name, "yes"); } bits = bits / 2; } Record sqlParams = new Record(2); if (this.inputDatabase.TableExists("Control")) { using (View dialogView = this.inputDatabase.OpenView("SELECT * FROM `Control` WHERE `Dialog_` = ?")) { ArrayList processedControls = new ArrayList(); using (View dialogControlView = this.inputDatabase.OpenView("SELECT * FROM `Control` WHERE `Dialog_` = ? AND `Control` = ?")) { string nextControl = "_WiX_First_Control_Never_Matches_Anything_"; sqlParams.SetString(1, record[(int)MsiInterop.Dialog.Dialog]); while (0 < nextControl.Length && nextControl != firstControl) { if ("_WiX_First_Control_Never_Matches_Anything_" == nextControl) { nextControl = firstControl; } sqlParams.SetString(2, nextControl); dialogControlView.Execute(sqlParams); Record dialogControlRecord; dialogControlView.Fetch(out dialogControlRecord); if (null == dialogControlRecord) { // TODO: throw an exception - Fail "Control " & nextControl & " not found" break; } this.ProcessControl(writer, parentWriter, dialogControlRecord, defaultControl, cancelControl, false); processedControls.Add(nextControl); nextControl = dialogControlRecord[(int)MsiInterop.Control.ControlNext]; } } dialogView.Execute(sqlParams); Record dialogRecord; while (dialogView.Fetch(out dialogRecord)) { string nextControl = dialogRecord[(int)MsiInterop.Control.Control]; if (null != nextControl && nextControl != firstControl && !processedControls.Contains(nextControl)) { this.ProcessControl(writer, parentWriter, dialogRecord, defaultControl, cancelControl, true); } } } } if (this.generateFragments) { // terminate the UI element writer.WriteEndElement(); // terminate the fragment writer.WriteEndElement(); } writer.WriteEndElement(); } } }
/// <summary> /// Process control. /// </summary> /// <param name="writer">XmlWriter where the Intermediate should persist itself as XML.</param> /// <param name="parentWriter">XmlWriter where the Intermediate should persist itself as XML.</param> /// <param name="record">Record from Control table.</param> /// <param name="defaultControl">Name of default control.</param> /// <param name="cancelControl">Name of cancel control.</param> /// <param name="tabDisabled">Specifies if control should not be tabbed to.</param> private void ProcessControl(XmlWriter writer, XmlWriter parentWriter, Record record, string defaultControl, string cancelControl, bool tabDisabled) { const string tableName = "Control"; const string tableNameDependent = "CheckBox"; bool hasCheckBoxTable = false; if (!this.inputDatabase.TableExists(tableName)) { return; } if (this.inputDatabase.TableExists(tableNameDependent)) { hasCheckBoxTable = true; } string id = record[(int)MsiInterop.Control.Control]; string controlType = record[(int)MsiInterop.Control.Type]; string text = record[(int)MsiInterop.Control.Text]; if (!(this.skipInstallShield && 0 < text.IndexOf("InstallShield"))) { writer.WriteStartElement(tableName); this.core.WriteAttributeString(writer, "Id", id); this.core.WriteAttributeString(writer, "Type", controlType); this.core.WriteAttributeString(writer, "X", record[(int)MsiInterop.Control.X]); this.core.WriteAttributeString(writer, "Y", record[(int)MsiInterop.Control.Y]); this.core.WriteAttributeString(writer, "Width", record[(int)MsiInterop.Control.Width]); this.core.WriteAttributeString(writer, "Height", record[(int)MsiInterop.Control.Height]); this.core.WriteAttributeString(writer, "Property", record[(int)MsiInterop.Control.Property]); string[] helpArray = record[(int)MsiInterop.Control.Help].Split('|'); if (0 < helpArray.Length) { this.core.WriteAttributeString(writer, "ToolTip", helpArray[0]); if (1 < helpArray.Length && 0 < helpArray[1].Length) { this.core.WriteAttributeString(writer, "Help", helpArray[1]); } } if (hasCheckBoxTable && "CheckBox" == controlType) { using (View checkBoxView = this.inputDatabase.OpenExecuteView(String.Concat("SELECT `Value` FROM `", tableNameDependent, "` WHERE `Property`='", record[(int)MsiInterop.Control.Property], "'"))) { Record checkBoxRecord; while (checkBoxView.Fetch(out checkBoxRecord)) // will return a single value or will fail { this.core.WriteAttributeString(writer, "CheckBoxValue", checkBoxRecord[1]); // hard coded value because select (above) is for a single column } } } if (id == defaultControl) { this.core.WriteAttributeString(writer, "Default", "yes"); } if (id == cancelControl) { this.core.WriteAttributeString(writer, "Cancel", "yes"); } // TODO: test Attributes for blowing up when null, error violation of SDK constraint "This must be a non-negative number" this.ProcessControlAttributes(writer, controlType, Convert.ToInt32(record[(int)MsiInterop.Control.Attributes]), tabDisabled); if (0 < record[(int)MsiInterop.Control.Text].Length) { if (20 >= text.Length && !this.NeedsEscape(text)) { this.core.WriteAttributeString(writer, "Text", text); } else { writer.WriteStartElement("Text"); writer.WriteCData(text); writer.WriteEndElement(); } } string property = record[(int)MsiInterop.Control.Property]; if (0 < property.Length) { if ("ListBox" == controlType) { this.ProcessControlGroupTable(writer, "ListBox", record[(int)MsiInterop.Control.Property]); } else if ("RadioButtonGroup" == controlType) { this.ProcessControlGroupTable(writer, "RadioButton", record[(int)MsiInterop.Control.Property]); } else if ("ListView" == controlType) { this.ProcessControlGroupTable(writer, "ListView", record[(int)MsiInterop.Control.Property]); } else if ("ComboBox" == controlType) { this.ProcessControlGroupTable(writer, "ComboBox", record[(int)MsiInterop.Control.Property]); } else { this.EmitProperty(parentWriter, this.nonuiproperties, property); } } /* broken and not needed right now if ("Icon" == controlType && 0 < record[(int)MsiInterop.Control.Text].Length) { this.ProcessBinaryTable(writer, BinaryType.Binary, record[(int)MsiInterop.Control.Text]); } */ Record sqlParams = new Record(2); sqlParams.SetString(1, record[(int)MsiInterop.Control.Dialog]); sqlParams.SetString(2, record[(int)MsiInterop.Control.Control]); if (this.inputDatabase.TableExists("ControlEvent")) { using (View publishView = this.inputDatabase.OpenView("SELECT * FROM `ControlEvent` WHERE `Dialog_` = ? AND `Control_` = ? ORDER BY `Ordering`")) { publishView.Execute(sqlParams); Record publishRecord; while (publishView.Fetch(out publishRecord)) { writer.WriteStartElement("Publish"); string controlEvent = publishRecord[(int)MsiInterop.ControlEvent.Event]; if (controlEvent.StartsWith("[") && controlEvent.EndsWith("]")) { this.core.WriteAttributeString(writer, "Property", controlEvent.Substring(1, controlEvent.Length - 2)); } else { this.core.WriteAttributeString(writer, "Event", controlEvent); } this.core.WriteAttributeString(writer, "Value", publishRecord[(int)MsiInterop.ControlEvent.Argument]); writer.WriteCData(publishRecord[(int)MsiInterop.ControlEvent.Condition]); writer.WriteEndElement(); } } } if (this.inputDatabase.TableExists("EventMapping")) { using (View subscribeView = this.inputDatabase.OpenView("SELECT * FROM `EventMapping` WHERE `Dialog_` = ? AND `Control_` = ?")) { subscribeView.Execute(sqlParams); Record subscribeRecord; while (subscribeView.Fetch(out subscribeRecord)) { writer.WriteStartElement("Subscribe"); this.core.WriteAttributeString(writer, "Event", subscribeRecord[(int)MsiInterop.EventMapping.Event]); this.core.WriteAttributeString(writer, "Attribute", subscribeRecord[(int)MsiInterop.EventMapping.Attribute]); writer.WriteEndElement(); } } } if (this.inputDatabase.TableExists("ControlCondition")) { using (View conditionView = this.inputDatabase.OpenView("SELECT * FROM `ControlCondition` WHERE `Dialog_` = ? AND `Control_` = ?")) { conditionView.Execute(sqlParams); Record conditionRecord; while (conditionView.Fetch(out conditionRecord)) { writer.WriteStartElement("Condition"); this.core.WriteAttributeString(writer, "Action", conditionRecord[(int)MsiInterop.ControlCondition.Action].ToLower()); writer.WriteCData(conditionRecord[(int)MsiInterop.ControlCondition.Condition]); writer.WriteEndElement(); } } } writer.WriteEndElement(); } else { this.core.OnMessage(WixWarnings.FilteringInstallShieldStuff(null, WarningLevel.Major, "Control", String.Concat(record[(int)MsiInterop.Control.Dialog], "::", record[(int)MsiInterop.Control.Control]))); } }
/// <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> /// 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(); } }