From dbde9e7104b907bbbaea17e21247d8cafc8b3a4c Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 14 Oct 2017 16:12:07 -0700 Subject: Massive refactoring to introduce the concept of IBackend --- .../Bind/MergeModulesCommand.cs | 351 +++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs new file mode 100644 index 00000000..624cbb43 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -0,0 +1,351 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Databases +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using System.Xml; + using System.Xml.XPath; + using WixToolset.Clr.Interop; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.MergeMod; + using WixToolset.Msi; + using WixToolset.Core.Native; + using WixToolset.Core.Bind; + + /// + /// Update file information. + /// + internal class MergeModulesCommand + { + public IEnumerable FileFacades { private get; set; } + + public Output Output { private get; set; } + + public string OutputPath { private get; set; } + + public IEnumerable SuppressedTableNames { private get; set; } + + public string TempFilesLocation { private get; set; } + + public void Execute() + { + Debug.Assert(OutputType.Product == this.Output.Type); + + Table wixMergeTable = this.Output.Tables["WixMerge"]; + Table wixFeatureModulesTable = this.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 = MsmInterop.GetMsmMerge(); + + logPath = Path.Combine(this.TempFilesLocation, "merge.log"); + merge.OpenLog(logPath); + logOpen = true; + + merge.OpenDatabase(this.OutputPath); + 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) + { + Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); + continue; + } + + Messaging.Instance.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 + Messaging.Instance.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]) + { + Messaging.Instance.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: + Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString())); + break; + case MsmErrorType.msmErrorFeatureRequired: + Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id)); + break; + case MsmErrorType.msmErrorLanguageFailed: + Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorLanguageUnsupported: + Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorResequenceMerge: + Messaging.Instance.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 + { + Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); + } + break; + case MsmErrorType.msmErrorPlatformMismatch: + Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); + break; + default: + Messaging.Instance.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) + { + Messaging.Instance.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 (Messaging.Instance.EncounteredError) + { + return; + } + + using (Database db = new Database(this.OutputPath, OpenDatabase.Direct)) + { + Table suppressActionTable = this.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) + { + Messaging.Instance.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 this.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; + } + + Messaging.Instance.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 + Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); + using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) + { + foreach (FileFacade file in this.FileFacades) + { + if (!file.FromModule) + { + continue; + } + + using (Record record = new Record(1)) + { + record.SetString(1, file.File.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, file.File.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 == file.File.Compressed) + { + // these are mutually exclusive + attributes |= MsiInterop.MsidbFileAttributesCompressed; + attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; + } + else if (YesNoType.No == file.File.Compressed) + { + // these are mutually exclusive + attributes |= MsiInterop.MsidbFileAttributesNoncompressed; + attributes &= ~MsiInterop.MsidbFileAttributesCompressed; + } + else // not specified + { + Debug.Assert(YesNoType.NotSet == file.File.Compressed); + + // clear any compression bits + attributes &= ~MsiInterop.MsidbFileAttributesCompressed; + attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; + } + + recordUpdate.SetInteger(2, attributes); + + view.Modify(ModifyView.Update, recordUpdate); + } + } + } + + db.Commit(); + } + } + } +} -- cgit v1.2.3-55-g6feb