// 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();
}
}
}
}