From 3f583916719eeef598d10a5d4e14ef14f008243b Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 11 May 2021 07:36:37 -0700 Subject: Merge Dtf --- .../EmbeddedExternalUI.cs | 173 +++++++++ .../WixToolsetTests.Dtf.WindowsInstaller/Schema.cs | 238 ++++++++++++ .../WindowsInstallerTest.cs | 409 +++++++++++++++++++++ .../WindowsInstallerTransactions.cs | 161 ++++++++ .../WindowsInstallerUtils.cs | 174 +++++++++ .../WixToolsetTests.Dtf.WindowsInstaller.csproj | 34 ++ 6 files changed, 1189 insertions(+) create mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs create mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs create mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs create mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs create mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs create mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj (limited to 'src/dtf/WixToolsetTests.Dtf.WindowsInstaller') diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs new file mode 100644 index 00000000..b0fc00a8 --- /dev/null +++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs @@ -0,0 +1,173 @@ +// 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.Dtf.Test +{ + using System; + using System.IO; + using System.Reflection; + using System.Windows.Forms; + using System.Globalization; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using WixToolset.Dtf.WindowsInstaller; + using View = WixToolset.Dtf.WindowsInstaller.View; + + [TestClass] + public class EmbeddedExternalUI + { + const InstallLogModes TestLogModes = + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData; + +#if DEBUG + const string EmbeddedUISampleBinDir = @"..\..\build\debug\"; +#else + const string EmbeddedUISampleBinDir = @"..\..\build\release\"; +#endif + + [TestMethod] + [Ignore] // Requires elevation. + public void EmbeddedUISingleInstall() + { + string dbFile = "EmbeddedUISingleInstall.msi"; + string productCode; + + string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir); + string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + using (Record uiRec = new Record(5)) + { + uiRec[1] = "TestEmbeddedUI"; + uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll"; + uiRec[3] = 1; + uiRec[4] = (int) ( + EmbeddedExternalUI.TestLogModes | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + uiRec.SetStream(5, Path.Combine(uiDir, uiFile)); + db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec); + } + + db.Commit(); + } + + Installer.SetInternalUI(InstallUIOptions.Full); + + ProductInstallation installation = new ProductInstallation(productCode); + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting."); + + Exception caughtEx = null; + try + { + Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "install.log"); + Installer.InstallProduct(dbFile, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx); + + Assert.IsTrue(installation.IsInstalled, "Checking that product is installed."); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine("==================================================================="); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + + try + { + Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "uninstall.log"); + Installer.InstallProduct(dbFile, "REMOVE=All"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while uninstalling product: " + caughtEx); + } + + // This test does not pass if run normally. + // It only passes when a failure is injected into the EmbeddedUI launcher. + ////[TestMethod] + public void EmbeddedUIInitializeFails() + { + string dbFile = "EmbeddedUIInitializeFails.msi"; + string productCode; + + string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir); + string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll"; + + // A number that will be used to check whether a type 19 CA runs. + const string magicNumber = "3.14159265358979323846264338327950"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + const string failureActionName = "EmbeddedUIInitializeFails"; + db.Execute("INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) " + + "VALUES ('{0}', 19, '', 'Logging magic number: {1}')", failureActionName, magicNumber); + + // This type 19 CA (launch condition) is given a condition of 'UILevel = 3' so that it only runs if the + // installation is running in BASIC UI mode, which is what we expect if the EmbeddedUI fails to initialize. + db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) " + + "VALUES ('{0}', 'UILevel = 3', 1)", failureActionName); + + productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + using (Record uiRec = new Record(5)) + { + uiRec[1] = "TestEmbeddedUI"; + uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll"; + uiRec[3] = 1; + uiRec[4] = (int)( + EmbeddedExternalUI.TestLogModes | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + uiRec.SetStream(5, Path.Combine(uiDir, uiFile)); + db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec); + } + + db.Commit(); + } + + Installer.SetInternalUI(InstallUIOptions.Full); + + ProductInstallation installation = new ProductInstallation(productCode); + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting."); + + string logFile = "install.log"; + Exception caughtEx = null; + try + { + Installer.EnableLog(EmbeddedExternalUI.TestLogModes, logFile); + Installer.InstallProduct(dbFile, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(InstallerException), + "Excpected InstallerException installing product; caught: " + caughtEx); + + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed."); + + string logText = File.ReadAllText(logFile); + Assert.IsTrue(logText.Contains(magicNumber), "Checking that the type 19 custom action ran."); + } + } +} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs new file mode 100644 index 00000000..26c172c9 --- /dev/null +++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs @@ -0,0 +1,238 @@ +// 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.Dtf.Test +{ + using System; + using System.IO; + using System.Collections.Generic; + using System.Text; + using WixToolset.Dtf.WindowsInstaller; + + public static class Schema + { + public static IList Tables + { + get + { + return new TableInfo[] + { + Binary, + Component, + CustomAction, + Directory, + EmbeddedUI, + Feature, + FeatureComponents, + File, + InstallExecuteSequence, + Media, + Property, + Registry + }; + } + } + + #region Table data + + public static TableInfo Binary { get { return new TableInfo( + "Binary", + new ColumnInfo[] + { + new ColumnInfo("Name", typeof(String), 72, true), + new ColumnInfo("Data", typeof(Stream), 0, true), + }, + new string[] { "Name" }); + } } + + public static TableInfo Component { get { return new TableInfo( + "Component", + new ColumnInfo[] + { + new ColumnInfo("Component", typeof(String), 72, true), + new ColumnInfo("ComponentId", typeof(String), 38, false), + new ColumnInfo("Directory_", typeof(String), 72, true), + new ColumnInfo("Attributes", typeof(Int16), 2, true), + new ColumnInfo("Condition", typeof(String), 255, false), + new ColumnInfo("KeyPath", typeof(String), 72, false), + }, + new string[] { "Component" }); + } } + + public static TableInfo CustomAction { get { return new TableInfo( + "CustomAction", + new ColumnInfo[] + { + new ColumnInfo("Action", typeof(String), 72, true), + new ColumnInfo("Type", typeof(Int16), 2, true), + new ColumnInfo("Source", typeof(String), 64, false), + new ColumnInfo("Target", typeof(String), 255, false), + }, + new string[] { "Action" }); + } } + + public static TableInfo Directory { get { return new TableInfo( + "Directory", + new ColumnInfo[] + { + new ColumnInfo("Directory", typeof(String), 72, true), + new ColumnInfo("Directory_Parent", typeof(String), 72, false), + new ColumnInfo("DefaultDir", typeof(String), 255, true, false, true), + }, + new string[] { "Directory" }); + } } + + public static TableInfo EmbeddedUI { get { return new TableInfo( + "MsiEmbeddedUI", + new ColumnInfo[] + { + new ColumnInfo("MsiEmbeddedUI", typeof(String), 72, true), + new ColumnInfo("FileName", typeof(String), 72, true), + new ColumnInfo("Attributes", typeof(Int16), 2, true), + new ColumnInfo("MessageFilter", typeof(Int32), 4, false), + new ColumnInfo("Data", typeof(Stream), 0, true), + }, + new string[] { "MsiEmbeddedUI" }); + } } + + public static TableInfo Feature { get { return new TableInfo( + "Feature", + new ColumnInfo[] + { + new ColumnInfo("Feature", typeof(String), 38, true), + new ColumnInfo("Feature_Parent", typeof(String), 38, false), + new ColumnInfo("Title", typeof(String), 64, false, false, true), + new ColumnInfo("Description", typeof(String), 64, false, false, true), + new ColumnInfo("Display", typeof(Int16), 2, false), + new ColumnInfo("Level", typeof(Int16), 2, true), + new ColumnInfo("Directory_", typeof(String), 72, false), + new ColumnInfo("Attributes", typeof(Int16), 2, true), + }, + new string[] { "Feature" }); + } } + + public static TableInfo FeatureComponents { get { return new TableInfo( + "FeatureComponents", + new ColumnInfo[] + { + new ColumnInfo("Feature_", typeof(String), 38, true), + new ColumnInfo("Component_", typeof(String), 72, true), + }, + new string[] { "Feature_", "Component_" }); + } } + + public static TableInfo File { get { return new TableInfo( + "File", + new ColumnInfo[] + { + new ColumnInfo("File", typeof(String), 72, true), + new ColumnInfo("Component_", typeof(String), 72, true), + new ColumnInfo("FileName", typeof(String), 255, true, false, true), + new ColumnInfo("FileSize", typeof(Int32), 4, true), + new ColumnInfo("Version", typeof(String), 72, false), + new ColumnInfo("Language", typeof(String), 20, false), + new ColumnInfo("Attributes", typeof(Int16), 2, false), + new ColumnInfo("Sequence", typeof(Int16), 2, true), + }, + new string[] { "File" }); + } } + + public static TableInfo InstallExecuteSequence { get { return new TableInfo( + "InstallExecuteSequence", + new ColumnInfo[] + { + new ColumnInfo("Action", typeof(String), 72, true), + new ColumnInfo("Condition", typeof(String), 255, false), + new ColumnInfo("Sequence", typeof(Int16), 2, true), + }, + new string[] { "Action" }); + } } + + public static TableInfo Media { get { return new TableInfo( + "Media", + new ColumnInfo[] + { + new ColumnInfo("DiskId", typeof(Int16), 2, true), + new ColumnInfo("LastSequence", typeof(Int16), 2, true), + new ColumnInfo("DiskPrompt", typeof(String), 64, false, false, true), + new ColumnInfo("Cabinet", typeof(String), 255, false), + new ColumnInfo("VolumeLabel", typeof(String), 32, false), + new ColumnInfo("Source", typeof(String), 32, false), + }, + new string[] { "DiskId" }); + } } + + public static TableInfo Property { get { return new TableInfo( + "Property", + new ColumnInfo[] + { + new ColumnInfo("Property", typeof(String), 72, true), + new ColumnInfo("Value", typeof(String), 255, true), + }, + new string[] { "Property" }); + } } + + public static TableInfo Registry { get { return new TableInfo( + "Registry", + new ColumnInfo[] + { + new ColumnInfo("Registry", typeof(String), 72, true), + new ColumnInfo("Root", typeof(Int16), 2, true), + new ColumnInfo("Key", typeof(String), 255, true, false, true), + new ColumnInfo("Name", typeof(String), 255, false, false, true), + new ColumnInfo("Value", typeof(String), 0, false, false, true), + new ColumnInfo("Component_", typeof(String), 72, true), + }, + new string[] { "Registry" }); + } } + + #endregion + + } + + public class Action + { + public readonly string Name; + public readonly int Sequence; + + public Action(string name, int sequence) + { + this.Name = name; + this.Sequence = sequence; + } + + } + + public class Sequence + { + public static IList InstallExecute + { + get + { + return new Action[] + { + new Action("CostInitialize", 800), + new Action("FileCost", 900), + new Action("CostFinalize", 1000), + new Action("InstallValidate", 1400), + new Action("InstallInitialize", 1500), + new Action("ProcessComponents", 1600), + new Action("UnpublishComponents", 1700), + new Action("UnpublishFeatures", 1800), + new Action("RemoveRegistryValues", 2600), + new Action("RemoveFiles", 3500), + new Action("RemoveFolders", 3600), + new Action("CreateFolders", 3700), + new Action("MoveFiles", 3800), + new Action("InstallFiles", 4000), + new Action("WriteRegistryValues", 5000), + new Action("RegisterProduct", 6100), + new Action("PublishComponents", 6200), + new Action("PublishFeatures", 6300), + new Action("PublishProduct", 6400), + new Action("InstallFinalize", 6600), + }; + } + } + + } +} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs new file mode 100644 index 00000000..f994dfef --- /dev/null +++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs @@ -0,0 +1,409 @@ +// 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.Dtf.Test +{ + using System; + using System.IO; + using System.Windows.Forms; + using System.Globalization; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using WixToolset.Dtf.WindowsInstaller; + using View = WixToolset.Dtf.WindowsInstaller.View; + + [TestClass] + public class WindowsInstallerTest + { + public WindowsInstallerTest() + { + } + + [TestInitialize()] + public void Initialize() + { + } + + [TestCleanup()] + public void Cleanup() + { + } + + [TestMethod] + [Ignore] // Currently fails. + public void InstallerErrorMessages() + { + string msg3002 = Installer.GetErrorMessage(3002); + Console.WriteLine("3002=" + msg3002); + Assert.IsNotNull(msg3002); + Assert.IsTrue(msg3002.Length > 0); + } + + [TestMethod] + public void InstallerDatabaseSchema() + { + string dbFile = "InstallerDatabaseSchema.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + db.Commit(); + } + + Assert.IsTrue(File.Exists(dbFile), "Checking whether created database file " + dbFile + " exists."); + + using (Database db = new Database(dbFile, DatabaseOpenMode.ReadOnly)) + { + TableCollection tables = db.Tables; + Assert.AreEqual(Schema.Tables.Count, tables.Count, "Counting tables."); + Assert.AreEqual(Schema.Property.Columns.Count, tables["Property"].Columns.Count, "Counting columns in Property table."); + + foreach (TableInfo tableInfo in tables) + { + Console.WriteLine(tableInfo.Name); + foreach (ColumnInfo columnInfo in tableInfo.Columns) + { + Console.WriteLine("\t{0} {1}", columnInfo.Name, columnInfo.ColumnDefinitionString); + } + } + } + } + + [TestMethod] + public void InstallerViewTables() + { + string dbFile = "InstallerViewTables.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + db.Commit(); + + using (View view1 = db.OpenView("SELECT `Property`, `Value` FROM `Property` WHERE `Value` IS NOT NULL")) + { + IList viewTables = view1.Tables; + Assert.IsNotNull(viewTables); + Assert.AreEqual(1, viewTables.Count); + Assert.AreEqual("Property", viewTables[0].Name); + } + + using (View view2 = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES ('TestViewTables', 1)")) + { + IList viewTables = view2.Tables; + Assert.IsNotNull(viewTables); + Assert.AreEqual(1, viewTables.Count); + Assert.AreEqual("Property", viewTables[0].Name); + } + + using (View view3 = db.OpenView("UPDATE `Property` SET `Value` = 2 WHERE `Property` = 'TestViewTables'")) + { + IList viewTables = view3.Tables; + Assert.IsNotNull(viewTables); + Assert.AreEqual(1, viewTables.Count); + Assert.AreEqual("Property", viewTables[0].Name); + } + + using (View view4 = db.OpenView("alter table Property hold")) + { + IList viewTables = view4.Tables; + Assert.IsNotNull(viewTables); + Assert.AreEqual(1, viewTables.Count); + Assert.AreEqual("Property", viewTables[0].Name); + } + } + } + + [TestMethod] + public void InstallerInstallProduct() + { + string dbFile = "InstallerInstallProduct.msi"; + string productCode; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + db.Commit(); + } + + ProductInstallation installation = new ProductInstallation(productCode); + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting."); + + Installer.SetInternalUI(InstallUIOptions.Silent); + ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger, + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null."); + + Exception caughtEx = null; + try + { + Installer.InstallProduct(dbFile, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx); + + prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None); + Assert.AreEqual(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned."); + + Assert.IsTrue(installation.IsInstalled, "Checking that product is installed."); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine("==================================================================="); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + + ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger, + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null."); + + try + { + Installer.InstallProduct(dbFile, "REMOVE=All"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx); + + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed after removing."); + + prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None); + Assert.AreEqual(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned."); + } + + public static MessageResult ExternalUILogger( + InstallMessage messageType, + string message, + MessageButtons buttons, + MessageIcon icon, + MessageDefaultButton defaultButton) + { + Console.WriteLine("{0}: {1}", messageType, message); + return MessageResult.None; + } + + public static MessageResult ExternalUIRecordLogger( + InstallMessage messageType, + Record messageRecord, + MessageButtons buttons, + MessageIcon icon, + MessageDefaultButton defaultButton) + { + if (messageRecord != null) + { + if (messageRecord.FormatString.Length == 0 && messageRecord.FieldCount > 0) + { + messageRecord.FormatString = "1: [1] 2: [2] 3: [3] 4: [4] 5: [5]"; + } + Console.WriteLine("{0}: {1}", messageType, messageRecord.ToString()); + } + else + { + Console.WriteLine("{0}: (null)", messageType); + } + return MessageResult.None; + } + + [TestMethod] + [Ignore] // Currently fails. + public void InstallerMessageResources() + { + string message1101 = Installer.GetErrorMessage(1101); + Console.WriteLine("Message 1101: " + message1101); + Assert.IsNotNull(message1101); + Assert.IsTrue(message1101.Contains("file")); + + message1101 = Installer.GetErrorMessage(1101, CultureInfo.GetCultureInfo(1033)); + Console.WriteLine("Message 1101: " + message1101); + Assert.IsNotNull(message1101); + Assert.IsTrue(message1101.Contains("file")); + + string message2621 = Installer.GetErrorMessage(2621); + Console.WriteLine("Message 2621: " + message2621); + Assert.IsNotNull(message2621); + Assert.IsTrue(message2621.Contains("DLL")); + + string message3002 = Installer.GetErrorMessage(3002); + Console.WriteLine("Message 3002: " + message3002); + Assert.IsNotNull(message3002); + Assert.IsTrue(message3002.Contains("sequencing")); + } + + [TestMethod] + public void EnumComponentQualifiers() + { + foreach (ComponentInstallation comp in ComponentInstallation.AllComponents) + { + bool qualifiers = false; + foreach (ComponentInstallation.Qualifier qualifier in comp.Qualifiers) + { + if (!qualifiers) + { + Console.WriteLine(comp.Path); + qualifiers = true; + } + + Console.WriteLine("\t{0}: {1}", qualifier.QualifierCode, qualifier.Data); + } + } + } + + [TestMethod] + public void DeleteRecord() + { + string dbFile = "DeleteRecord.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + string query = "SELECT `Property`, `Value` FROM `Property` WHERE `Property` = 'UpgradeCode'"; + + using (View view = db.OpenView(query)) + { + view.Execute(); + + Record rec = view.Fetch(); + + Console.WriteLine("Calling ToString() : " + rec); + + view.Delete(rec); + } + + Assert.AreEqual(0, db.ExecuteStringQuery(query).Count); + } + } + + [TestMethod] + public void InsertRecordThenTryFormatString() + { + string dbFile = "InsertRecordThenTryFormatString.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + string parameterFormatString = "[1]"; + string[] properties = new string[] + { + "SonGoku", "Over 9000", + }; + + string query = "SELECT `Property`, `Value` FROM `Property`"; + + using (View view = db.OpenView(query)) + { + using (Record rec = new Record(2)) + { + rec[1] = properties[0]; + rec[2] = properties[1]; + rec.FormatString = parameterFormatString; + Console.WriteLine("Format String before inserting: " + rec.FormatString); + view.Insert(rec); + + Console.WriteLine("Format String after inserting: " + rec.FormatString); + // After inserting, the format string is invalid. + Assert.AreEqual(String.Empty, rec.ToString()); + + // Setting the format string manually makes it valid again. + rec.FormatString = parameterFormatString; + Assert.AreEqual(properties[0], rec.ToString()); + } + } + } + } + + [TestMethod] + public void SeekRecordThenTryFormatString() + { + string dbFile = "SeekRecordThenTryFormatString.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + string parameterFormatString = "[1]"; + string[] properties = new string[] + { + "SonGoku", "Over 9000", + }; + + string query = "SELECT `Property`, `Value` FROM `Property`"; + + using (View view = db.OpenView(query)) + { + using (Record rec = new Record(2)) + { + rec[1] = properties[0]; + rec[2] = properties[1]; + rec.FormatString = parameterFormatString; + Console.WriteLine("Record fields before seeking: " + rec[0] + " " + rec[1] + " " + rec[2]); + view.Seek(rec); + + //TODO: Why does view.Seek remove the record fields? + Console.WriteLine("Record fields after seeking: " + rec[0] + " " + rec[1] + " " + rec[2]); + // After inserting, the format string is invalid. + Assert.AreEqual(String.Empty, rec.ToString()); + } + } + } + } + + [TestMethod] + public void TestToString() + { + string defaultString = "1: "; + string vegetaShout = "It's OVER 9000!!"; + string gokuPowerLevel = "9001"; + string nappaInquiry = "Vegeta, what's the Scouter say about his power level?"; + string parameterFormatString = "[1]"; + + Record rec = new Record(1); + Assert.AreEqual(defaultString, rec.ToString(), "Testing default FormatString"); + + rec.FormatString = String.Empty; + Assert.AreEqual(defaultString, rec.ToString(), "Explicitly set the FormatString to the empty string."); + + rec.FormatString = vegetaShout; + Assert.AreEqual(vegetaShout, rec.ToString(), "Testing text only (empty FormatString)"); + + rec.FormatString = gokuPowerLevel; + Assert.AreEqual(gokuPowerLevel, rec.ToString(), "Testing numbers only from a record that wasn't fetched."); + + Record rec2 = new Record(nappaInquiry); + rec2.FormatString = parameterFormatString; + Assert.AreEqual(nappaInquiry, rec2.ToString(), "Testing text with a FormatString set."); + } + } +} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs new file mode 100644 index 00000000..3bdf5acd --- /dev/null +++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs @@ -0,0 +1,161 @@ +// 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.Dtf.Test +{ + using System; + using System.IO; + using System.Windows.Forms; + using System.Globalization; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using WixToolset.Dtf.WindowsInstaller; + using View = WixToolset.Dtf.WindowsInstaller.View; + + [TestClass] + public class WindowsInstallerTransactions + { + [TestInitialize()] + public void Initialize() + { + } + + [TestCleanup()] + public void Cleanup() + { + } + + [TestMethod] + [Ignore] // Requires elevation. + public void InstallerTransactTwoProducts() + { + string dbFile1 = "InstallerTransactProduct1.msi"; + string dbFile2 = "InstallerTransactProduct2.msi"; + string productCode1; + string productCode2; + + using (Database db1 = new Database(dbFile1, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db1); + WindowsInstallerUtils.CreateTestProduct(db1); + + productCode1 = db1.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + db1.Commit(); + } + + using (Database db2 = new Database(dbFile2, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db2); + WindowsInstallerUtils.CreateTestProduct(db2); + + productCode2 = db2.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + db2.Commit(); + } + + ProductInstallation installation1 = new ProductInstallation(productCode1); + ProductInstallation installation2 = new ProductInstallation(productCode2); + Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed before starting."); + Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed before starting."); + + Installer.SetInternalUI(InstallUIOptions.Silent); + ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger, + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null."); + + Transaction transaction = new Transaction("TestInstallTransaction", TransactionAttributes.None); + + Exception caughtEx = null; + try + { + Installer.InstallProduct(dbFile1, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product 1: " + caughtEx); + + Console.WriteLine(); + Console.WriteLine("==================================================================="); + Console.WriteLine(); + + try + { + Installer.InstallProduct(dbFile2, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product 2: " + caughtEx); + + transaction.Commit(); + transaction.Close(); + + prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None); + Assert.AreEqual(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned."); + + Assert.IsTrue(installation1.IsInstalled, "Checking that product 1 is installed."); + Assert.IsTrue(installation2.IsInstalled, "Checking that product 2 is installed."); + + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine("==================================================================="); + Console.WriteLine("==================================================================="); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + + ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger, + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null."); + + transaction = new Transaction("TestUninstallTransaction", TransactionAttributes.None); + + try + { + Installer.InstallProduct(dbFile1, "REMOVE=All"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while removing product 1: " + caughtEx); + + try + { + Installer.InstallProduct(dbFile2, "REMOVE=All"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while removing product 2: " + caughtEx); + + transaction.Commit(); + transaction.Close(); + + Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed after removing."); + Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed after removing."); + + prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None); + Assert.AreEqual(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned."); + } + } +} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs new file mode 100644 index 00000000..644f1988 --- /dev/null +++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs @@ -0,0 +1,174 @@ +// 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.Dtf.Test +{ + using System; + using System.Collections.Generic; + using System.Text; + using WixToolset.Dtf.WindowsInstaller; + + public class WindowsInstallerUtils + { + public static void InitializeProductDatabase(Database db) + { + InitializeProductDatabase(db, false); + } + + public static void InitializeProductDatabase(Database db, bool sixtyFourBit) + { + db.SummaryInfo.CodePage = (short) Encoding.Default.CodePage; + db.SummaryInfo.Title = "Windows Installer Test"; + db.SummaryInfo.Subject = db.SummaryInfo.Title; + db.SummaryInfo.Author = typeof(WindowsInstallerUtils).Assembly.FullName; + db.SummaryInfo.CreatingApp = db.SummaryInfo.Author; + db.SummaryInfo.Comments = typeof(WindowsInstallerUtils).FullName + ".CreateBasicDatabase()"; + db.SummaryInfo.Keywords = "Installer,MSI,Database"; + db.SummaryInfo.PageCount = 300; + db.SummaryInfo.WordCount = 0; + db.SummaryInfo.RevisionNumber = Guid.NewGuid().ToString("B").ToUpper(); + db.SummaryInfo.Template = (sixtyFourBit ? "x64" : "Intel") + ";0"; + + foreach (TableInfo tableInfo in Schema.Tables) + { + db.Execute(tableInfo.SqlCreateString); + } + + db.Execute("INSERT INTO `Directory` (`Directory`, `DefaultDir`) VALUES ('TARGETDIR', 'SourceDir')"); + db.Execute("INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES ('ProgramFilesFolder', 'TARGETDIR', '.')"); + + foreach (Action action in Sequence.InstallExecute) + { + db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('{0}', {1})", + action.Name, action.Sequence); + } + } + + public const string UpgradeCode = "{05955FE8-005F-4695-A81F-D559338065BB}"; + + public static void CreateTestProduct(Database db) + { + Guid productGuid = Guid.NewGuid(); + + string[] properties = new string[] + { + "ProductCode", productGuid.ToString("B").ToUpper(), + "UpgradeCode", UpgradeCode, + "ProductName", "Windows Installer Test Product " + productGuid.ToString("P").ToUpper(), + "ProductVersion", "1.0.0.0000", + }; + + using (View view = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)")) + { + using (Record rec = new Record(2)) + { + for (int i = 0; i < properties.Length; i += 2) + { + rec[1] = properties[i]; + rec[2] = properties[i + 1]; + view.Execute(rec); + } + } + } + + int randomId = new Random().Next(10000); + string productDir = "TestDir" + randomId; + db.Execute( + "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) " + + "VALUES ('TestDir', 'ProgramFilesFolder', 'TestDir|{0}:.')", productDir); + + string compId = Guid.NewGuid().ToString("B").ToUpper(); + db.Execute( + "INSERT INTO `Component` " + + "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " + + "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')", + "TestRegComp1", + compId, + "TestDir", + (int) ComponentAttributes.RegistryKeyPath, + "TestReg1"); + + string productReg = "TestReg" + randomId; + db.Execute( + "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}')", + "TestReg1", + -1, + @"Software\Microsoft\Windows Installer Test\" + productReg, + "TestRegComp1"); + + db.Execute( + "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})", + "TestFeature1", + "Test Feature 1", + 1, + (int) FeatureAttributes.None); + + db.Execute( + "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')", + "TestFeature1", + "TestRegComp1"); + } + + public static void AddFeature(Database db, string featureName) + { + db.Execute( + "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})", + featureName, + featureName, + 1, + (int) FeatureAttributes.None); + } + + public static void AddRegistryComponent(Database db, + string featureName, string compName, string compId, + string keyName, string keyValueName, string value) + { + db.Execute( + "INSERT INTO `Component` " + + "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " + + "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')", + compName, + compId, + "TestDir", + (int) ComponentAttributes.RegistryKeyPath, + compName + "Reg1"); + db.Execute( + "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Name`, `Value`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}', '{4}', '{5}')", + compName + "Reg1", + -1, + @"Software\Microsoft\Windows Installer Test\" + keyName, + keyValueName, + value, + compName); + db.Execute( + "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')", + featureName, + compName); + } + + public static void AddFileComponent(Database db, + string featureName, string compName, string compId, + string fileKey, string fileName) + { + db.Execute( + "INSERT INTO `Component` " + + "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " + + "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')", + compName, + compId, + "TestDir", + (int) ComponentAttributes.None, + fileKey); + db.Execute( + "INSERT INTO `File` " + + "(`File`, `Component_`, `FileName`, `FileSize`, `Attributes`, `Sequence`) " + + "VALUES ('{0}', '{1}', '{2}', 1, 0, 1)", + fileKey, + compName, + fileName); + db.Execute( + "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')", + featureName, + compName); + } + } +} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj new file mode 100644 index 00000000..dbddb682 --- /dev/null +++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj @@ -0,0 +1,34 @@ + + + + + + {16F5202F-9276-4166-975C-C9654BAF8012} + Library + WixToolsetTests.Dtf + WixToolsetTests.Dtf.WindowsInstaller + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb